aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMuhammad Haseeb Ahmad <mhahmad@google.com>2021-12-30 02:40:44 +0000
committerMuhammad Haseeb Ahmad <mhahmad@google.com>2021-12-30 02:57:51 +0000
commit5c6f4116994d03e47d625eda2b4b7d7fe9702a3c (patch)
treec5880647e8b29782d15be0c99a60e56fed6f8a02
parentb997679abe998d84ad4b9c3e6589342794d3bfcb (diff)
parent726cc6bd0c0e26378574f74b712de948e56664ec (diff)
downloadjazzer-api-5c6f4116994d03e47d625eda2b4b7d7fe9702a3c.tar.gz
Merge remote-tracking branch 'aosp/upstream-main' into master
Test: N/A, since just merging upstream. Test description will follow in next commit. Change-Id: I0351a1f3b6f665f5334a2b65b74ff7a7f044c0ba
-rw-r--r--.bazelrc46
-rw-r--r--.bazelversion1
-rw-r--r--.clang-format7
-rw-r--r--.github/workflows/check-formatting.yml41
-rw-r--r--.github/workflows/release.yml86
-rw-r--r--.github/workflows/run-all-tests.yml64
-rw-r--r--.gitignore3
-rw-r--r--BUILD.bazel82
-rw-r--r--CHANGELOG.md17
-rw-r--r--LICENSE202
-rw-r--r--README.md531
-rw-r--r--WORKSPACE.bazel108
-rw-r--r--agent/BUILD.bazel39
-rw-r--r--agent/agent_shade_rules4
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt160
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel15
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt201
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt182
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java32
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java26
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel28
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java211
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java22
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java22
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java22
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java22
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java20
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java444
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java39
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java39
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java39
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java39
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java24
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java499
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java183
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java31
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java117
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java31
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel17
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java267
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java619
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java229
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel40
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java215
-rwxr-xr-xagent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh18
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel45
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt53
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt229
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt90
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt35
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt201
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt119
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt48
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt386
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt45
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt258
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules1
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel18
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java159
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel48
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java33
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt166
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java83
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java82
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java29
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt54
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java35
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java74
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java26
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java330
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java91
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java47
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java35
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel10
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt102
-rw-r--r--agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt82
-rw-r--r--agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel13
-rw-r--r--agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp48
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java107
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel21
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel69
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java103
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java111
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java147
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java43
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java85
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel5
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java56
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java87
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java85
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java29
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel147
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java53
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java61
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java25
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java41
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java67
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt141
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt73
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java21
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt38
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java61
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java45
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java106
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt53
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java109
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt63
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java120
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java23
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java152
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt145
-rw-r--r--agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java49
-rw-r--r--bazel/BUILD.bazel6
-rw-r--r--bazel/FuzzTargetTestWrapper.java87
-rw-r--r--bazel/cc.bzl80
-rw-r--r--bazel/compat.bzl23
-rw-r--r--bazel/fuzz_target.bzl79
-rw-r--r--bazel/kotlin.bzl51
-rwxr-xr-xbazelisk-linux-amd64bin0 -> 5283840 bytes
-rw-r--r--deploy/BUILD.bazel13
-rwxr-xr-xdocker/build_all.sh19
-rw-r--r--docker/jazzer-autofuzz/Dockerfile30
-rwxr-xr-xdocker/jazzer-autofuzz/entrypoint.sh24
-rw-r--r--docker/jazzer/Dockerfile34
-rwxr-xr-xdocker/push_all.sh21
-rw-r--r--driver/BUILD.bazel265
-rw-r--r--driver/coverage_tracker.cpp215
-rw-r--r--driver/coverage_tracker.h58
-rw-r--r--driver/fuzz_target_runner.cpp398
-rw-r--r--driver/fuzz_target_runner.h76
-rw-r--r--driver/fuzzed_data_provider.cpp719
-rw-r--r--driver/fuzzed_data_provider.h44
-rw-r--r--driver/fuzzed_data_provider_test.cpp300
-rw-r--r--driver/java_reproducer.cpp79
-rw-r--r--driver/java_reproducer.h32
-rw-r--r--driver/java_reproducer_templates.h58
-rw-r--r--driver/jvm_tooling.cpp482
-rw-r--r--driver/jvm_tooling.h87
-rw-r--r--driver/jvm_tooling_test.cpp199
-rw-r--r--driver/libfuzzer_callbacks.cpp411
-rw-r--r--driver/libfuzzer_callbacks.h25
-rw-r--r--driver/libfuzzer_driver.cpp187
-rw-r--r--driver/libfuzzer_driver.h75
-rw-r--r--driver/libfuzzer_fuzz_target.cpp86
-rw-r--r--driver/sanitizer_hooks_with_pc.cpp187
-rw-r--r--driver/sanitizer_hooks_with_pc.h47
-rw-r--r--driver/sanitizer_hooks_with_pc_test.cpp188
-rw-r--r--driver/sanitizer_symbols.cpp29
-rw-r--r--driver/sanitizer_symbols_for_tests.cpp45
-rw-r--r--driver/signal_handler.cpp66
-rw-r--r--driver/signal_handler.h29
-rw-r--r--driver/test_main.cpp22
-rw-r--r--driver/testdata/BUILD.bazel10
-rw-r--r--driver/testdata/test/FuzzTargetWithCoverage.java28
-rw-r--r--driver/testdata/test/FuzzTargetWithDataProvider.java114
-rw-r--r--driver/testdata/test/FuzzTargetWithInit.java30
-rw-r--r--driver/testdata/test/ModifiedUtf8Encoder.java41
-rw-r--r--driver/testdata/test/PropertyPrinter.java22
-rw-r--r--driver/testdata/test/SimpleFuzzTarget.java25
-rw-r--r--driver/utils.cpp208
-rw-r--r--driver/utils.h32
-rw-r--r--examples/BUILD.bazel321
-rwxr-xr-xexamples/check_for_finding.sh41
-rw-r--r--examples/json_sanitizer_denylist_crashbin0 -> 38 bytes
-rw-r--r--examples/src/main/java/com/example/ExampleFuzzer.java40
-rw-r--r--examples/src/main/java/com/example/ExampleFuzzerHooks.java30
-rw-r--r--examples/src/main/java/com/example/ExampleFuzzerWithNative.java36
-rw-r--r--examples/src/main/java/com/example/ExampleOutOfMemoryFuzzer.java28
-rw-r--r--examples/src/main/java/com/example/ExamplePathTraversalFuzzer.java43
-rw-r--r--examples/src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java46
-rw-r--r--examples/src/main/java/com/example/ExampleStackOverflowFuzzer.java33
-rw-r--r--examples/src/main/java/com/example/ExampleValueProfileFuzzer.java53
-rw-r--r--examples/src/main/java/com/example/FastJsonFuzzer.java30
-rw-r--r--examples/src/main/java/com/example/GifImageParserFuzzer.java32
-rw-r--r--examples/src/main/java/com/example/JacksonCborFuzzer.java34
-rw-r--r--examples/src/main/java/com/example/JpegImageParserFuzzer.java45
-rw-r--r--examples/src/main/java/com/example/JsonSanitizerCrashFuzzer.java30
-rw-r--r--examples/src/main/java/com/example/JsonSanitizerDenylistFuzzer.java45
-rw-r--r--examples/src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java34
-rw-r--r--examples/src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java42
-rw-r--r--examples/src/main/java/com/example/KlaxonFuzzer.kt31
-rw-r--r--examples/src/main/java/com/example/Log4jFuzzer.java82
-rw-r--r--examples/src/main/java/com/example/TiffImageParserFuzzer.java31
-rw-r--r--examples/src/main/java/com/example/TurboJpegFuzzer.java59
-rw-r--r--examples/src/main/native/com/example/BUILD.bazel49
-rw-r--r--examples/src/main/native/com/example/com_example_ExampleFuzzerWithNative.cpp42
-rwxr-xr-xformat.sh14
-rw-r--r--init.bzl29
-rw-r--r--jazzer-api.pom38
-rw-r--r--maven.bzl38
-rw-r--r--maven_install.json328
-rw-r--r--repositories.bzl137
-rw-r--r--sanitizers/BUILD.bazel8
-rw-r--r--sanitizers/sanitizers.bzl24
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel17
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt168
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt83
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt102
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt34
-rw-r--r--sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt51
-rw-r--r--sanitizers/src/main/java/jaz/BUILD.bazel12
-rw-r--r--sanitizers/src/main/java/jaz/Ter.java24
-rw-r--r--sanitizers/src/main/java/jaz/Zer.java107
-rw-r--r--sanitizers/src/test/java/com/example/BUILD.bazel32
-rw-r--r--sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java49
-rw-r--r--sanitizers/src/test/java/com/example/InsecureEmailValidator.java36
-rw-r--r--sanitizers/src/test/java/com/example/ObjectInputStreamDeserialization.java32
-rw-r--r--sanitizers/src/test/java/com/example/ReflectiveCall.java32
-rw-r--r--third_party/BUILD.bazel14
-rw-r--r--third_party/asm.BUILD32
-rw-r--r--third_party/bazel-toolchain-export-dynamic-macos-asan.patch12
-rw-r--r--third_party/classgraph.BUILD8
-rw-r--r--third_party/gflags-use-double-dash-args.patch11
-rw-r--r--third_party/jacoco-make-probe-adapter-subclassable.patch69
-rw-r--r--third_party/jacoco-make-probe-inserter-subclassable.patch109
-rw-r--r--third_party/jacoco_internal.BUILD18
-rw-r--r--third_party/libFuzzer.BUILD27
-rw-r--r--third_party/libjpeg_turbo.BUILD69
-rw-r--r--third_party/typetools.BUILD7
226 files changed, 19340 insertions, 0 deletions
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 00000000..6c60e6c2
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,46 @@
+build --incompatible_strict_action_env
+build --sandbox_tmpfs_path=/tmp
+build --enable_platform_specific_config
+build -c opt
+
+# C/C++
+# Only relevant for tests and their dependencies. Everything that external
+# repositories can reference must build without this, e.g., by using a
+# transition.
+build:linux --cxxopt='-std=c++17'
+build:macos --cxxopt='-std=c++17'
+build:windows --cxxopt='/std:c++17'
+build --repo_env=CC=clang
+build --incompatible_enable_cc_toolchain_resolution
+# Requires a relatively modern clang.
+build:ci --features=layering_check
+
+# Java
+build --java_language_version=8
+build --tool_java_language_version=9
+
+# Windows
+# Only compiles with clang on Windows.
+build:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl
+build:windows --extra_execution_platforms=//:x64_windows-clang-cl
+build:windows --features=static_link_msvcrt
+# Required as PATH doubles as the shared library search path on Windows and the
+# Java agent functionality depends on system-provided shared libraries.
+test:windows --noincompatible_strict_action_env
+run:windows --noincompatible_strict_action_env
+
+# Toolchain
+# Since the toolchain is conditional on OS and architecture, set it on the particular GitHub Action.
+build:toolchain --//third_party:toolchain
+
+# CI tests (not using the toolchain to test OSS-Fuzz & local compatibility)
+build:ci --bes_results_url=https://app.buildbuddy.io/invocation/
+build:ci --bes_backend=grpcs://cloud.buildbuddy.io
+build:ci --remote_cache=grpcs://cloud.buildbuddy.io
+build:ci --remote_timeout=3600
+
+# Maven publishing (local only, requires GPG signature)
+build:maven --config=toolchain
+build:maven --stamp
+build:maven --define "maven_repo=https://oss.sonatype.org/service/local/staging/deploy/maven2"
+build:maven --java_runtime_version=localjdk_8
diff --git a/.bazelversion b/.bazelversion
new file mode 100644
index 00000000..af8c8ec7
--- /dev/null
+++ b/.bazelversion
@@ -0,0 +1 @@
+4.2.2
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..bdbc38d2
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,7 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+---
+Language: Java
+BasedOnStyle: Google
+...
diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml
new file mode 100644
index 00000000..c32aaff1
--- /dev/null
+++ b/.github/workflows/check-formatting.yml
@@ -0,0 +1,41 @@
+name: Check formatting
+
+# Controls when the action will run.
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+ workflow_dispatch:
+
+jobs:
+ check_formatting:
+ runs-on: ubuntu-20.04
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Setup Go environment
+ uses: actions/setup-go@v2
+ with:
+ go-version: '^1.15.5'
+
+ - name: Install formatters
+ run: |
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
+ sudo apt-get install software-properties-common
+ sudo add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main'
+ sudo apt-get install clang-format-13
+ curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint && chmod a+x ktlint && sudo mv ktlint /usr/bin/ktlint
+ go get -u github.com/google/addlicense
+ go get github.com/bazelbuild/buildtools/buildifier
+
+ - name: Run format.sh and print changes
+ run: |
+ ./format.sh
+ clang-format --version
+ git diff
+
+ - name: Check for changes
+ run: "[ $(git status --porcelain | wc -l) -eq 0 ]"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..b2d5566d
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,86 @@
+name: Release
+
+on:
+ workflow_dispatch:
+
+jobs:
+
+ build_release:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-10.15, windows-2016]
+ include:
+ - os: ubuntu-latest
+ arch: "linux"
+ bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux"
+ - os: macos-10.15
+ arch: "macos-x86_64"
+ bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin"
+ - os: windows-2016
+ arch: "windows"
+ bazel_args: ""
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up JDK
+ uses: actions/setup-java@v1
+ with:
+ java-version: 8
+
+ - name: Build
+ run: |
+ bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --java_runtime_version=localjdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} //agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar //:jazzer_release
+ cp -L bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer_deploy.jar replayer.jar
+ cp -L bazel-bin/jazzer_release.tar.gz release-${{ matrix.arch }}.tar.gz
+
+ - name: Upload replayer
+ uses: actions/upload-artifact@v2
+ with:
+ name: replayer_${{ matrix.arch }}
+ path: replayer.jar
+
+ - name: Upload release tar
+ uses: actions/upload-artifact@v2
+ with:
+ name: jazzer_releases
+ path: release-${{ matrix.arch}}.tar.gz
+
+ merge_replayer_jars:
+ runs-on: ubuntu-latest
+ needs: build_release
+
+ steps:
+ - name: Download macOS jar
+ uses: actions/download-artifact@v2
+ with:
+ name: replayer_darwin
+ path: replayer_darwin
+
+ - name: Download Linux jar
+ uses: actions/download-artifact@v2
+ with:
+ name: replayer_linux
+ path: replayer_linux
+
+ - name: Download Windows jar
+ uses: actions/download-artifact@v2
+ with:
+ name: replayer_windows
+ path: replayer_windows
+
+ - name: Merge jars
+ run: |
+ mkdir merged
+ unzip -o replayer_darwin/replayer.jar -d merged
+ unzip -o replayer_linux/replayer.jar -d merged
+ unzip -o replayer_windows/replayer.jar -d merged
+ jar cvmf merged/META-INF/MANIFEST.MF replayer.jar -C merged .
+
+ - name: Upload merged jar
+ uses: actions/upload-artifact@v2
+ with:
+ name: replayer
+ path: replayer.jar
+
diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml
new file mode 100644
index 00000000..2af6f5ec
--- /dev/null
+++ b/.github/workflows/run-all-tests.yml
@@ -0,0 +1,64 @@
+name: Build all targets and run all tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+ workflow_dispatch:
+
+jobs:
+
+ build_and_test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-11, windows-latest]
+ jdk: [8, 15, 17]
+ exclude:
+ # Only test against JDK 15 with Ubuntu since this is what OSS-Fuzz uses.
+ - os: macos-11
+ jdk: 15
+ - os: windows-latest
+ jdk: 15
+ include:
+ - os: ubuntu-latest
+ arch: "linux"
+ cache: "/home/runner/.cache/bazel-disk"
+ - os: macos-11
+ arch: "macos-x86_64"
+ # Always use the toolchain as UBSan produces linker errors with Apple LLVM 13.
+ bazel_args: "--config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-darwin"
+ cache: "/private/var/tmp/bazel-disk"
+ - os: windows-latest
+ arch: "windows"
+ cache: "%HOME%/bazel-disk"
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up JDK
+ uses: actions/setup-java@v1
+ with:
+ java-version: ${{ matrix.jdk }}
+
+ - name: Mount Bazel disk cache
+ uses: actions/cache@v2
+ with:
+ path: ${{ matrix.cache }}
+ key: bazel-disk-cache-${{ matrix.arch }}-${{ matrix.jdk }}
+
+ - name: Build
+ run: bazelisk build --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} --java_runtime_version=localjdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} //...
+
+ - name: Test
+ run: bazelisk test --config=ci --remote_header=x-buildbuddy-api-key=${{ secrets.BUILDBUDDY_API_KEY }} --disk_cache=${{ matrix.cache }} --java_runtime_version=localjdk_${{ matrix.jdk }} ${{ matrix.bazel_args }} //...
+
+ - name: Upload test logs
+ if: always()
+ uses: actions/upload-artifact@v2
+ with:
+ name: testlogs-${{ matrix.arch }}-${{ matrix.jdk }}
+ # https://github.com/actions/upload-artifact/issues/92#issuecomment-711107236
+ path: bazel-testlogs*/**/test.log
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..e992903a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/bazel-*
+.ijwb
+.clwb
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 00000000..a5ba2f52
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,82 @@
+load("@bazel_tools//tools/build_defs/pkg:pkg.bzl", "pkg_tar")
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "define_kt_toolchain")
+load("@io_bazel_rules_kotlin//kotlin/internal:opts.bzl", "kt_javac_options", "kt_kotlinc_options")
+
+exports_files(["LICENSE"])
+
+kt_kotlinc_options(
+ name = "kotlinc_options",
+)
+
+kt_javac_options(
+ name = "default_javac_options",
+)
+
+define_kt_toolchain(
+ name = "kotlin_toolchain",
+ api_version = "1.5",
+ javac_options = ":default_javac_options",
+ jvm_target = "1.8",
+ kotlinc_options = ":kotlinc_options",
+ language_version = "1.5",
+)
+
+pkg_tar(
+ name = "jazzer_release",
+ srcs = [
+ "//agent:jazzer_agent_deploy.jar",
+ "//agent:jazzer_api_deploy.jar",
+ "//driver:jazzer_driver",
+ ],
+ extension = "tar.gz",
+ mode = "0777",
+ remap_paths = {
+ "agent/jazzer_agent_deploy.jar": "jazzer_agent_deploy.jar",
+ "agent/jazzer_api_deploy.jar": "jazzer_api_deploy.jar",
+ "driver/jazzer_driver": "jazzer",
+ },
+ strip_prefix = "./",
+)
+
+alias(
+ name = "jazzer",
+ actual = "//driver:jazzer_driver",
+)
+
+alias(
+ name = "jazzer_asan",
+ actual = "//driver:jazzer_driver_asan",
+)
+
+alias(
+ name = "jazzer_ubsan",
+ actual = "//driver:jazzer_driver_ubsan",
+)
+
+exports_files([
+ "jazzer-api.pom",
+])
+
+config_setting(
+ name = "clang",
+ flag_values = {"@bazel_tools//tools/cpp:compiler": "clang"},
+ visibility = ["//visibility:public"],
+)
+
+alias(
+ name = "clang_on_linux",
+ actual = select({
+ ":clang": "@platforms//os:linux",
+ "//conditions:default": ":clang",
+ }),
+ visibility = ["//visibility:public"],
+)
+
+platform(
+ name = "x64_windows-clang-cl",
+ constraint_values = [
+ "@platforms//cpu:x86_64",
+ "@platforms//os:windows",
+ "@bazel_tools//tools/cpp:clang-cl",
+ ],
+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..516b32c0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+**Note:** Before version 1.0.0, every release may contain breaking changes.
+
+## Version 0.9.1
+
+* **Breaking change**: The static `fuzzerTestOneInput` method in a fuzz target now has to return `void` instead of `boolean`. Fuzz targets that previously returned `true` should now throw an exception or use `assert`.
+* Fixed: `jazzer` wrapper can find `jazzer_driver` even if not in the working directory
+* Fixed: Switch instrumentation no longer causes an out-of-bounds read in the driver
+* Feature: `assert` can be used in fuzz targets
+* Feature: Coverage is now collision-free and more fine-grained (based on [JaCoCo](https://www.eclemma.org/jacoco/))
+* API: Added `pickValue(Collection c)` and `consumeChar(char min, char max)` to `FuzzedDataProvider`
+* API: Added `FuzzerSecurityIssue*` exceptions to allow specifiying the severity of findings
+
+## Version 0.9.0
+
+* Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..7a4a3ea2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..07bbdda1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,531 @@
+<img src="https://www.code-intelligence.com/hubfs/Logos/CI%20Logos/Jazzer_einfach.png" height=150px alt="Jazzer logo">
+
+
+# Jazzer
+[![Maven Central](https://img.shields.io/maven-central/v/com.code-intelligence/jazzer-api)](https://search.maven.org/search?q=g:com.code-intelligence%20a:jazzer-api)
+![GitHub Actions](https://github.com/CodeIntelligenceTesting/jazzer/workflows/Build%20all%20targets%20and%20run%20all%20tests/badge.svg)
+[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/java-example.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:java-example)
+
+Jazzer is a coverage-guided, in-process fuzzer for the JVM platform developed by [Code Intelligence](https://code-intelligence.com).
+It is based on [libFuzzer](https://llvm.org/docs/LibFuzzer.html) and brings many of its instrumentation-powered mutation features to the JVM.
+
+The JVM bytecode is executed inside the fuzzer process, which ensures fast execution speeds and allows seamless fuzzing of
+native libraries.
+
+Jazzer supports Linux and (experimentally) macOS 10.15 and 11 as well as Windows, all on the x64 architecture.
+
+## News: Jazzer available in OSS-Fuzz
+
+[Code Intelligence](https://code-intelligence.com) and Google have teamed up to bring support for Java, Kotlin, and other JVM-based languages to [OSS-Fuzz](https://github.com/google/oss-fuzz), Google's project for large-scale fuzzing of open-souce software. Read [the blogpost](https://security.googleblog.com/2021/03/fuzzing-java-in-oss-fuzz.html) over at the Google Security Blog.
+
+If you want to learn more about Jazzer and OSS-Fuzz, [watch the FuzzCon 2020 talk](https://www.youtube.com/watch?v=SmH3Ys_k8vA&list=PLI0R_0_8-TV55gJU-UXrOzZoPbVOj1CW6&index=3) by [Abhishek Arya](https://twitter.com/infernosec) and [Fabian Meumertzheim](https://twitter.com/fhenneke).
+
+## Installation
+
+The preferred way to install Jazzer is to compile it from source using [Bazel](https://bazel.build), but binary distributions for x64 Linux as well as a Docker image are also available.
+Note that these binaries might be outdated as Jazzer follows the "Live at Head" philosophy - you should be able to just checkout the latest commit from `main` and build it.
+
+Support for Jazzer has recently been added to [rules_fuzzing](https://github.com/bazelbuild/rules_fuzzing), the official Bazel rules for fuzzing. See their README for instructions on how to use Jazzer in a Java Bazel project.
+
+### Using Docker
+
+The "distroless" Docker image [cifuzz/jazzer](https://hub.docker.com/r/cifuzz/jazzer) includes Jazzer together with OpenJDK 11. Just mount a directory containing your compiled fuzz target into the container under `/fuzzing` by running:
+
+```sh
+docker run -v path/containing/the/application:/fuzzing cifuzz/jazzer <arguments>
+```
+
+If Jazzer produces a finding, the input that triggered it will be available in the same directory.
+
+### Using Bazel
+
+Jazzer has the following dependencies when being built from source:
+
+* JDK 8 or later (e.g. [OpenJDK](https://openjdk.java.net/))
+* [Clang](https://clang.llvm.org/) 9.0 or later (using a recent version is strongly recommended)
+
+#### Linux
+
+Jazzer uses [Bazelisk](https://github.com/bazelbuild/bazelisk) to automatically download and install Bazel on Linux.
+Building Jazzer from source and running it thus only requires the following assuming the dependencies are installed:
+
+```bash
+git clone https://github.com/CodeIntelligenceTesting/jazzer
+cd jazzer
+# Note the double dash used to pass <arguments> to Jazzer rather than Bazel.
+./bazelisk-linux-amd64 run //:jazzer -- <arguments>
+```
+
+If you prefer to build binaries that can be run without Bazel, use the following command to build your own archive with release binaries:
+
+```bash
+$ ./bazelisk-linux-amd64 build //:jazzer_release
+...
+INFO: Found 1 target...
+Target //:jazzer_release up-to-date:
+ bazel-bin/jazzer_release.tar.gz
+...
+```
+
+This will print the path of a `jazzer_release.tar.gz` archive that contains the same binaries that would be part of a release.
+
+#### macOS
+
+Since Jazzer does not ship the macOS version of [Bazelisk](https://github.com/bazelbuild/bazelisk), a tool that automatically downloads and installs the correct version of Bazel, download [the most recent release](https://github.com/bazelbuild/bazelisk/releases) of `bazelisk-darwin`.
+Afterwards, clone Jazzer and run it via:
+
+```bash
+git clone https://github.com/CodeIntelligenceTesting/jazzer
+cd jazzer
+# Note the double dash used to pass <arguments> to Jazzer rather than Bazel.
+/path/to/bazelisk-darwin run //:jazzer -- <arguments>
+```
+
+If you prefer to build binaries that can be run without Bazel, use the following command to build your own archive with release binaries:
+
+```bash
+$ /path/to/bazelisk-darwin build //:jazzer_release
+...
+INFO: Found 1 target...
+Target //:jazzer_release up-to-date:
+ bazel-bin/jazzer_release.tar.gz
+...
+```
+
+This will print the path of a `jazzer_release.tar.gz` archive that contains the same binaries that would be part of a release.
+
+The build may fail with the clang shipped with Xcode. If you encounter issues during the build, add `--config=toolchain`
+right after `run` or `build` in the `bazelisk` commands above to use a checked-in toolchain that is known to work.
+
+### Using the provided binaries
+
+Binary releases are available under [Releases](https://github.com/CodeIntelligenceTesting/jazzer/releases) and are built
+using an [LLVM 11 Bazel toolchain](https://github.com/CodeIntelligenceTesting/llvm-toolchain).
+
+The binary distributions of Jazzer consist of the following components:
+
+- `jazzer_driver` - native binary that interfaces between libFuzzer and the JVM fuzz target
+- `jazzer_agent_deploy.jar` - Java agent that performs bytecode instrumentation and tracks coverage
+- `jazzer_api_deploy.jar` - contains convenience methods for creating fuzz targets and defining custom hooks
+- `jazzer` - convenience shell script that runs the Jazzer driver with the local JRE shared libraries added to `LD_LIBRARY_PATH`
+
+The additional release artifact `examples_deploy.jar` contains most of the examples and can be used to run them without having to build them (see Examples below).
+
+After unpacking the archive, run Jazzer via
+
+```bash
+./jazzer <arguments>
+```
+
+If this leads to an error message saying that `libjvm.so` has not been found, the path to the local JRE needs to be
+specified in the `JAVA_HOME` environment variable.
+
+## Examples
+
+Multiple examples for instructive and real-world Jazzer fuzz targets can be found in the `examples/` directory.
+A toy example can be run as follows:
+
+```bash
+# Using Bazelisk:
+./bazelisk-linux-amd64 run //examples:ExampleFuzzer
+# Using the binary release and examples_deploy.jar:
+./jazzer --cp=examples_deploy.jar
+```
+
+This should produce output similar to the following:
+
+```
+INFO: Loaded 1 hooks from com.example.ExampleFuzzerHooks
+INFO: Instrumented com.example.ExampleFuzzer (took 81 ms, size +83%)
+INFO: libFuzzer ignores flags that start with '--'
+INFO: Seed: 2735196724
+INFO: Loaded 1 modules (65536 inline 8-bit counters): 65536 [0xe387b0, 0xe487b0),
+INFO: Loaded 1 PC tables (65536 PCs): 65536 [0x7f9353eff010,0x7f9353fff010),
+INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
+INFO: A corpus is not provided, starting from an empty corpus
+#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 94Mb
+#1562 NEW cov: 4 ft: 4 corp: 2/14b lim: 17 exec/s: 0 rss: 98Mb L: 13/13 MS: 5 ShuffleBytes-CrossOver-InsertRepeatedBytes-ShuffleBytes-CMP- DE: "magicstring4"-
+#1759 REDUCE cov: 4 ft: 4 corp: 2/13b lim: 17 exec/s: 0 rss: 99Mb L: 12/12 MS: 2 ChangeBit-EraseBytes-
+#4048 NEW cov: 6 ft: 6 corp: 3/51b lim: 38 exec/s: 0 rss: 113Mb L: 38/38 MS: 4 ChangeBit-ChangeByte-CopyPart-CrossOver-
+#4055 REDUCE cov: 6 ft: 6 corp: 3/49b lim: 38 exec/s: 0 rss: 113Mb L: 36/36 MS: 2 ShuffleBytes-EraseBytes-
+#4266 REDUCE cov: 6 ft: 6 corp: 3/48b lim: 38 exec/s: 0 rss: 113Mb L: 35/35 MS: 1 EraseBytes-
+#4498 REDUCE cov: 6 ft: 6 corp: 3/47b lim: 38 exec/s: 0 rss: 114Mb L: 34/34 MS: 2 EraseBytes-CopyPart-
+#4764 REDUCE cov: 6 ft: 6 corp: 3/46b lim: 38 exec/s: 0 rss: 115Mb L: 33/33 MS: 1 EraseBytes-
+#5481 REDUCE cov: 6 ft: 6 corp: 3/44b lim: 43 exec/s: 0 rss: 116Mb L: 31/31 MS: 2 InsertByte-EraseBytes-
+#131072 pulse cov: 6 ft: 6 corp: 3/44b lim: 1290 exec/s: 65536 rss: 358Mb
+
+== Java Exception: java.lang.IllegalStateException: mustNeverBeCalled has been called
+ at com.example.ExampleFuzzer.mustNeverBeCalled(ExampleFuzzer.java:38)
+ at com.example.ExampleFuzzer.fuzzerTestOneInput(ExampleFuzzer.java:32)
+DEDUP_TOKEN: eb6ee7d9b256590d
+== libFuzzer crashing input ==
+MS: 1 CMP- DE: "\x00C"-; base unit: 04e0ccacb50424e06e45f6184ad45895b6b8df8f
+0x6d,0x61,0x67,0x69,0x63,0x73,0x74,0x72,0x69,0x6e,0x67,0x34,0x74,0x72,0x69,0x6e,0x67,0x34,0x74,0x69,0x67,0x34,0x7b,0x0,0x0,0x43,0x34,0xa,0x0,0x0,0x0,
+magicstring4tring4tig4{\x00\x00C4\x0a\x00\x00\x00
+artifact_prefix='./'; Test unit written to crash-efea1e8fc83a15217d512e20d964040a68a968c3
+Base64: bWFnaWNzdHJpbmc0dHJpbmc0dGlnNHsAAEM0CgAAAA==
+reproducer_path='.'; Java reproducer written to Crash_efea1e8fc83a15217d512e20d964040a68a968c3.java
+```
+
+Here you can see the usual libFuzzer output in case of a crash, augmented with JVM-specific information.
+Instead of a native stack trace, the details of the uncaught Java exception that caused the crash are printed, followed by the fuzzer input that caused the exception to be thrown (if it is not too long).
+More information on what hooks and Java reproducers are can be found below.
+
+See `examples/BUILD.bazel` for the list of all possible example targets.
+
+## Usage
+
+### Creating a fuzz target
+
+Jazzer requires a JVM class containing the entry point for the fuzzer. This is commonly referred to as a "fuzz target" and
+may be as simple as the following Java example:
+
+```java
+package com.example.MyFirstFuzzTarget;
+
+public class MyFirstFuzzTarget {
+ public static void fuzzerTestOneInput(byte[] input) {
+ ...
+ // Call the function under test with arguments derived from input and
+ // throw an exception if something unwanted happens.
+ ...
+ }
+}
+```
+
+A Java fuzz target class needs to define exactly one of the following functions:
+
+* `public static void fuzzerTestOneInput(byte[] input)`: Ideal for fuzz targets that naturally work on raw byte input (e.g.
+ image parsers).
+* `public static void fuzzerTestOneInput(com.code_intelligence.api.FuzzedDataProvider data)`: A variety of types of "fuzzed
+ data" is made available via the `FuzzedDataProvider` interface (see below for more information on this interface).
+
+The fuzzer will repeatedly call this function with generated inputs. All unhandled exceptions are caught and
+reported as errors.
+
+The optional functions `public static void fuzzerInitialize()` or `public static void fuzzerInitialize(String[] args)`
+can be defined if initial setup is required. These functions will be called once before
+the first call to `fuzzerTestOneInput`.
+
+The optional function `public static void fuzzerTearDown()` will be run just before the JVM is shut down.
+
+#### Kotlin
+
+An example of a Kotlin fuzz target can be found in
+[KlaxonFuzzer.kt](https://github.com/CodeIntelligenceTesting/jazzer/tree/main/examples/src/main/java/com/example/KlaxonFuzzer.kt).
+
+### Running the fuzzer
+
+The fuzz target needs to be compiled and packaged into a `.jar` archive. Assuming that this archive is called
+`fuzz_target.jar` and depends on libraries available as `lib1.jar` and `lib2.jar`, fuzzing is started by
+invoking Jazzer with the following arguments:
+
+```bash
+--cp=fuzz_target.jar:lib1.jar:lib2.jar --target_class=com.example.MyFirstFuzzTarget <optional_corpus_dir>
+```
+
+The fuzz target class can optionally be specified by adding it as the value of the `Jazzer-Fuzz-Target-Class` attribute
+in the JAR's manifest. If there is only a single such attribute among all manifests of JARs on the classpath, Jazzer will
+use its value as the fuzz target class.
+
+Bazel produces the correct type of `.jar` from a `java_binary` target with `create_executable = False` and
+`deploy_manifest_lines = ["Jazzer-Fuzz-Target-Class: com.example.MyFirstFuzzTarget"]` by adding the suffix `_deploy.jar`
+to the target name.
+
+### Fuzzed Data Provider
+
+For most non-trivial fuzz targets it is necessary to further process the byte array passed from the fuzzer, for example
+to extract multiple values or convert the input into a valid `java.lang.String`. We provide functionality similar to
+[atheris'](https://github.com/google/atheris) `FuzzedDataProvider` and libFuzzer's `FuzzedDataProvider.h` to simplify
+the task of writing JVM fuzz targets.
+
+If the function `public static void fuzzerTestOneInput(FuzzedDataProvider data)` is defined in the fuzz target, it will
+be passed an object implementing `com.code_intelligence.jazzer.api.FuzzedDataProvider` that allows _consuming_ the raw fuzzer
+input as values of common types. This can look as follows:
+
+```java
+package com.example.MySecondFuzzTarget;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public class MySecondFuzzTarget {
+ public static void callApi(int val, String text) {
+ ...
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ callApi1(data.consumeInt(), data.consumeRemainingAsString());
+ }
+}
+```
+
+The `FuzzedDataProvider` interface definition is contained in `jazzer_api_deploy.jar` in the binary release and can be
+built by the Bazel target `//agent:jazzer_api_deploy.jar`. It is also available from
+[Maven Central](https://search.maven.org/search?q=g:com.code-intelligence%20a:jazzer-api).
+For additional information, see the
+[javadocs](https://codeintelligencetesting.github.io/jazzer-api/com/code_intelligence/jazzer/api/FuzzedDataProvider.html).
+
+It is highly recommended to use `FuzzedDataProvider` for generating `java.lang.String` objects inside the fuzz target
+instead of converting the raw byte array to directly via a `String` constructor as the `FuzzedDataProvider` implementation is
+engineered to minimize copying and generate both valid and invalid ASCII-only and Unicode strings.
+
+### Autofuzz mode
+
+The Autofuzz mode enables fuzzing arbitrary methods without having to manually create fuzz targets.
+Instead, Jazzer will attempt to generate suitable and varied inputs to a specified methods using only public API functions available on the classpath.
+
+To use Autofuzz, specify the `--autofuzz` flag and provide a fully qualified method reference, e.g.:
+```
+--autofuzz=org.apache.commons.imaging.Imaging::getBufferedImage
+```
+If there are multiple overloads and you want Jazzer to only fuzz one, you can optionally specify the signature of the method to fuzz:
+```
+--autofuzz=org.apache.commons.imaging.Imaging::getBufferedImage(java.io.InputStream,java.util.Map)
+```
+The format of the signature agrees with that obtained from the part after the `#` of the link to the Javadocs for the particular method.
+
+Under the hood, jazzer tries various ways of creating objects from the fuzzer input. For example, if a parameter is an
+interface or an abstract class, it will look for all concrete implementing classes on the classpath.
+Jazzer can also create objects from classes that follow the [builder design pattern](https://www.baeldung.com/creational-design-patterns#builder)
+or have a default constructor and use setters to set the fields.
+
+Creating objects from fuzzer input can lead to many reported exceptions.
+Jazzer addresses this issue by ignoring exceptions that the target method declares to throw.
+In addition to that, you can provide a list of exceptions to be ignored during fuzzing via the `--autofuzz_ignore` flag in the form of a comma-separated list.
+You can specify concrete exceptions (e.g., `java.lang.NullPointerException`), in which case also subclasses of these exception classes will be ignored, or glob patterns to ignore all exceptions in a specific package (e.g. `java.lang.*` or `com.company.**`).
+
+When fuzzing with `--autofuzz`, Jazzer automatically enables the `--keep_going` mode to keep fuzzing indefinitely after the first finding.
+Set `--keep_going=N` explicitly to stop after the `N`-th finding.
+
+#### Docker
+To facilitate using the Autofuzz mode, there is a docker image that you can use to fuzz libraries just by providing their Maven coordinates.
+The dependencies will then be downloaded and autofuzzed:
+
+```sh
+docker run cifuzz/jazzer-autofuzz <Maven coordinates> --autofuzz=<method reference> <further arguments>
+```
+
+As an example, you can autofuzz the `json-sanitizer` library as follows:
+```sh
+docker run -it cifuzz/jazzer-autofuzz \
+ com.mikesamuel:json-sanitizer:1.2.0 \
+ com.google.json.JsonSanitizer::sanitize \
+ --autofuzz_ignore=java.lang.ArrayIndexOutOfBoundsException \
+ --keep_going=1
+```
+
+####
+
+### Reproducing a bug
+
+When Jazzer manages to find an input that causes an uncaught exception or a failed assertion, it prints a Java
+stack trace and creates two files that aid in reproducing the crash without Jazzer:
+
+* `crash-<sha1_of_input>` contains the raw bytes passed to the fuzz target (just as with libFuzzer C/C++ fuzz targets).
+ The crash can be reproduced with Jazzer by passing the path to the crash file as the only positional argument.
+* `Crash-<sha1_of_input>.java` contains a class with a `main` function that invokes the fuzz target with the
+ crashing input. This is especially useful if using `FuzzedDataProvider` as the raw bytes of the input do not
+ directly correspond to the values consumed by the fuzz target. The `.java` file can be compiled with just
+ the fuzz target and its dependencies in the classpath (plus `jazzer_api_deploy.jar` if using `FuzzedDataProvider).
+
+### Minimizing a crashing input
+
+Every crash stack trace is accompanied by a `DEDUP_TOKEN` that uniquely identifies the relevant parts of the stack
+trace. This value is used by libFuzzer while minimizing a crashing input to ensure that the smaller inputs reproduce
+the "same" bug. To minimize a crashing input, execute Jazzer with the following arguments in addition to `--cp` and
+`--target_class`:
+
+```bash
+-minimize_crash=1 <path/to/crashing_input>
+```
+
+### Parallel execution
+
+libFuzzer offers the `-fork=N` and `-jobs=N` flags for parallel fuzzing, both of which are also supported by Jazzer.
+
+### Limitations
+
+Jazzer currently maintains coverage information in a global variable that is shared among threads. This means that while
+fuzzing multi-threaded fuzz targets is theoretically possible, the reported coverage information may be misleading.
+
+## Findings
+
+Jazzer has so far uncovered the following vulnerabilities and bugs:
+
+| Project | Bug | Status | CVE | found by |
+| ------- | -------- | ------ | --- | -------- |
+| [jhy/jsoup](https://github.com/jhy/jsoup) | More than 19 Bugs found in HTML and XML parser | [fixed](https://github.com/jhy/jsoup/security/advisories/GHSA-m72m-mhq2-9p6c) | [CVE-2021-37714](https://nvd.nist.gov/vuln/detail/CVE-2021-37714) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | Infinite loop when loading a crafted 7z | fixed | [CVE-2021-35515](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35515) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | `OutOfMemoryError` when loading a crafted 7z | fixed | [CVE-2021-35516](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35516) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | Infinite loop when loading a crafted TAR | fixed | [CVE-2021-35517](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-35517) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | `OutOfMemoryError` when loading a crafted ZIP | fixed | [CVE-2021-36090](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-36090) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/PDFBox](https://pdfbox.apache.org/) | Infinite loop when loading a crafted PDF | fixed | [CVE-2021-27807](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-27807) | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/PDFBox](https://pdfbox.apache.org/) | OutOfMemoryError when loading a crafted PDF | fixed | [CVE-2021-27906](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-27906) | [Code Intelligence](https://code-intelligence.com) |
+| [netplex/json-smart-v1](https://github.com/netplex/json-smart-v1) <br/> [netplex/json-smart-v2](https://github.com/netplex/json-smart-v2) | `JSONParser#parse` throws an undeclared exception | [fixed](https://github.com/netplex/json-smart-v2/issues/60) | [CVE-2021-27568](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-27568) | [@GanbaruTobi](https://github.com/GanbaruTobi) |
+| [OWASP/json-sanitizer](https://github.com/OWASP/json-sanitizer) | Output can contain`</script>` and `]]>`, which allows XSS | [fixed](https://groups.google.com/g/json-sanitizer-support/c/dAW1AeNMoA0) | [CVE-2021-23899](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-23899) | [Code Intelligence](https://code-intelligence.com) |
+| [OWASP/json-sanitizer](https://github.com/OWASP/json-sanitizer) | Output can be invalid JSON and undeclared exceptions can be thrown | [fixed](https://groups.google.com/g/json-sanitizer-support/c/dAW1AeNMoA0) | [CVE-2021-23900](https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-23900) | [Code Intelligence](https://code-intelligence.com) |
+| [alibaba/fastjon](https://github.com/alibaba/fastjson) | `JSON#parse` throws undeclared exceptions | [fixed](https://github.com/alibaba/fastjson/issues/3631) | | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | Infinite loop and `OutOfMemoryError` in `TarFile` | [fixed](https://issues.apache.org/jira/browse/COMPRESS-569) | | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-compress](https://commons.apache.org/proper/commons-compress/) | `NullPointerException` in `ZipFile`| [fixed](https://issues.apache.org/jira/browse/COMPRESS-568) | | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/commons-imaging](https://commons.apache.org/proper/commons-imaging/) | Parsers for multiple image formats throw undeclared exceptions | [reported](https://issues.apache.org/jira/browse/IMAGING-279?jql=project%20%3D%20%22Commons%20Imaging%22%20AND%20reporter%20%3D%20Meumertzheim%20) | | [Code Intelligence](https://code-intelligence.com) |
+| [Apache/PDFBox](https://pdfbox.apache.org/) | Various undeclared exceptions | [fixed](https://issues.apache.org/jira/browse/PDFBOX-5108?jql=project%20%3D%20PDFBOX%20AND%20reporter%20in%20(Meumertzheim)) | | [Code Intelligence](https://code-intelligence.com) |
+| [cbeust/klaxon](https://github.com/cbeust/klaxon) | Default parser throws runtime exceptions | [fixed](https://github.com/cbeust/klaxon/pull/330) | | [Code Intelligence](https://code-intelligence.com) |
+| [FasterXML/jackson-dataformats-binary](https://github.com/FasterXML/jackson-dataformats-binary) | `CBORParser` throws an undeclared exception due to missing bounds checks when parsing Unicode | [fixed](https://github.com/FasterXML/jackson-dataformats-binary/issues/236) | | [Code Intelligence](https://code-intelligence.com) |
+| [FasterXML/jackson-dataformats-binary](https://github.com/FasterXML/jackson-dataformats-binary) | `CBORParser` throws an undeclared exception on dangling arrays | [fixed](https://github.com/FasterXML/jackson-dataformats-binary/issues/240) | | [Code Intelligence](https://code-intelligence.com) |
+| [ngageoint/tiff-java](https://github.com/ngageoint/tiff-java) | `readTiff ` Index Out Of Bounds | [fixed](https://github.com/ngageoint/tiff-java/issues/38) | | [@raminfp](https://github.com/raminfp) |
+| [google/re2j](https://github.com/google/re2j) | `NullPointerException` in `Pattern.compile` | [reported](https://github.com/google/re2j/issues/148) | | [@schirrmacher](https://github.com/schirrmacher) |
+| [google/gson](https://github.com/google/gson) | `ArrayIndexOutOfBounds` in `ParseString` | [fixed](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=40838) | | [@DavidKorczynski](https://twitter.com/Davkorcz) |
+
+As Jazzer is used to fuzz JVM projects in OSS-Fuzz, an additional list of bugs can be found [on the OSS-Fuzz issue tracker](https://bugs.chromium.org/p/oss-fuzz/issues/list?q=proj%3A%22json-sanitizer%22%20OR%20proj%3A%22fastjson2%22%20OR%20proj%3A%22jackson-core%22%20OR%20proj%3A%22jackson-dataformats-binary%22%20OR%20proj%3A%22jackson-dataformats-xml%22%20OR%20proj%3A%22apache-commons%22%20OR%20proj%3A%22jsoup%22&can=1).
+
+If you find bugs with Jazzer, we would like to hear from you!
+Feel free to [open an issue](https://github.com/CodeIntelligenceTesting/jazzer/issues/new) or submit a pull request.
+
+## Advanced Options
+
+Various command line options are available to control the instrumentation and fuzzer execution. Since Jazzer is a
+libFuzzer-compiled binary, all positional and single dash command-line options are parsed by libFuzzer. Therefore, all
+Jazzer options are passed via double dash command-line flags, i.e., as `--option=value` (note the `=` instead of a space).
+
+A full list of command-line flags can be printed with the `--help` flag. For the available libFuzzer options please refer
+to [its documentation](https://llvm.org/docs/LibFuzzer.html) for a detailed description.
+
+### Passing JVM arguments
+
+Arguments for the JVM started by Jazzer can be supplied via the `--jvm_args` argument.
+Multiple arguments are delimited by the classpath separator, which is `;` on Windows and `:` else.
+For example, to enable preview features as well as set a maximum heap size, add the following to the Jazzer invocation:
+
+```bash
+# Windows
+--jvm_args=--enable-preview;-Xmx1000m
+# Linux & macOS
+--jvm_args=--enable-preview:-Xmx1000m
+```
+
+### Coverage Instrumentation
+
+The Jazzer agent inserts coverage markers into the JVM bytecode during class loading. libFuzzer uses this information
+to guide its input mutations towards increased coverage.
+
+It is possible to restrict instrumentation to only a subset of classes with the `--instrumentation_includes` flag. This
+is especially useful if coverage inside specific packages is of higher interest, e.g., the user library under test rather than an
+external parsing library in which the fuzzer is likely to get lost. Similarly, there is `--instrumentation_excludes` to
+exclude specific classes from instrumentation. Both flags take a list of glob patterns for the java class name separated
+by colon:
+
+```bash
+--instrumentation_includes=com.my_com.**:com.other_com.** --instrumentation_excludes=com.my_com.crypto.**
+```
+
+By default, JVM-internal classes and Java as well as Kotlin standard library classes are not instrumented, so these do not
+need to be excluded manually.
+
+### Trace Instrumentation
+
+The agent adds additional hooks for tracing compares, integer divisions, switch statements and array indices.
+These hooks correspond to [clang's data flow hooks](https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-data-flow).
+The particular instrumentation types to apply can be specified using the `--trace` flag, which accepts the following values:
+
+* `cov`: AFL-style edge coverage
+* `cmp`: compares (int, long, String) and switch cases
+* `div`: divisors in integer divisions
+* `gep`: constant array indexes
+* `indir`: call through `Method#invoke`
+* `all`: shorthand to apply all available instrumentations (except `gep`)
+
+Multiple instrumentation types can be combined with a colon.
+
+### Value Profile
+
+The run-time flag `-use_value_profile=1` enables [libFuzzer's value profiling mode](https://llvm.org/docs/LibFuzzer.html#value-profile).
+When running with this flag, the feedback about compares and constants received from Jazzer's trace instrumentation is
+associated with the particular bytecode location and used to provide additional coverage instrumentation.
+See [ExampleValueProfileFuzzer.java](https://github.com/CodeIntelligenceTesting/jazzer/tree/main/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java)
+for a fuzz target that would be very hard to fuzz without value profile.
+
+As passing the bytecode location back to libFuzzer requires inline assembly and may thus not be fully portable, it can be disabled
+via the flag `--nofake_pcs`.
+
+### Custom Hooks
+
+In order to obtain information about data passed into functions such as `String.equals` or `String.startsWith`, Jazzer
+hooks invocations to these methods. This functionality is also available to fuzz targets, where it can be used to implement
+custom sanitizers or stub out methods that block the fuzzer from progressing (e.g. checksum verifications or random number generation).
+See [ExampleFuzzerHooks.java](https://github.com/CodeIntelligenceTesting/jazzer/tree/main/examples/src/main/java/com/example/ExampleFuzzerHooks.java)
+for an example of such a hook. An example for a sanitizer can be found in
+[ExamplePathTraversalFuzzerHooks.java](https://github.com/CodeIntelligenceTesting/jazzer/tree/main/examples/src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java).
+
+Method hooks can be declared using the `@MethodHook` annotation defined in the `com.code_intelligence.jazzer.api` package,
+which is contained in `jazzer_api_deploy.jar` (binary release) or built by the target `//agent:jazzer_api_deploy.jar` (Bazel).
+It is also available from
+[Maven Central](https://search.maven.org/search?q=g:com.code-intelligence%20a:jazzer-api).
+See the [javadocs of the `@MethodHook` API](https://codeintelligencetesting.github.io/jazzer-api/com/code_intelligence/jazzer/api/MethodHook.html)
+for more details.
+
+To use the compiled method hooks they have to be available on the classpath provided by `--cp` and can then be loaded by providing the
+flag `--custom_hooks`, which takes a colon-separated list of names of classes to load hooks from.
+This list of custom hooks can alternatively be specified via the `Jazzer-Hook-Classes` attribute in the fuzz target
+JAR's manifest.
+
+### Suppressing stack traces
+
+With the flag `--keep_going=N` Jazzer continues fuzzing until `N` unique stack traces have been encountered.
+
+Particular stack traces can also be ignored based on their `DEDUP_TOKEN` by passing a comma-separated list of tokens
+via `--ignore=<token_1>,<token2>`.
+
+## Advanced fuzz targets
+
+### Fuzzing with Native Libraries
+
+Jazzer supports fuzzing of native libraries loaded by the JVM, for example via `System.load()`. For the fuzzer to get
+coverage feedback, these libraries have to be compiled with `-fsanitize=fuzzer-no-link`.
+
+Additional sanitizers such as AddressSanitizer or UndefinedBehaviorSanitizer are often desirable to uncover bugs inside
+the native libraries. The required compilation flags for native libraries are as follows:
+ - *AddressSanitizer*: `-fsanitize=fuzzer-no-link,address`
+ - *UndefinedBehaviorSanitizer*: `-fsanitize=fuzzer-no-link,undefined` (add `-fno-sanitize-recover=all` to crash on UBSan reports)
+
+Then, use the appropriate driver `//:jazzer_asan` or `//:jazzer_ubsan`.
+
+**Note:** Sanitizers other than AddressSanitizer and UndefinedBehaviorSanitizer are not yet supported.
+Furthermore, due to the nature of the JVM's GC, LeakSanitizer reports too many false positives to be useful and is thus disabled.
+
+The fuzz targets `ExampleFuzzerWithNativeASan` and `ExampleFuzzerWithNativeUBSan` in the `examples/` directory contain
+minimal working examples for fuzzing with native libraries. Also see `TurboJpegFuzzer` for a real-world example.
+
+### Fuzzing with Custom Mutators
+
+LibFuzzer API offers two functions to customize the mutation strategy which is especially useful when fuzzing functions
+that require structured input. Jazzer does not define `LLVMFuzzerCustomMutator` nor `LLVMFuzzerCustomCrossOver` and
+leaves the mutation strategy entirely to libFuzzer. However, custom mutators can easily be integrated by
+compiling a mutator library which defines `LLVMFuzzerCustomMutator` (and optionally `LLVMFuzzerCustomCrossOver`) and
+pre-loading the mutator library:
+
+```bash
+# Using Bazel:
+LD_PRELOAD=libcustom_mutator.so ./bazelisk-linux-amd64 run //:jazzer -- <arguments>
+# Using the binary release:
+LD_PRELOAD=libcustom_mutator.so ./jazzer <arguments>
+```
+
+## Credit
+
+The following developers have contributed to Jazzer:
+
+[Sergej Dechand](https://github.com/serj),
+[Christian Hartlage](https://github.com/dende),
+[Fabian Meumertzheim](https://github.com/fmeum),
+[Sebastian Pöplau](https://github.com/sebastianpoeplau),
+[Mohammed Qasem](https://github.com/mohqas),
+[Simon Resch](https://github.com/simonresch),
+[Henrik Schnor](https://github.com/henrikschnor),
+[Khaled Yakdan](https://github.com/kyakdan)
+
+The LLVM-style edge coverage instrumentation for JVM bytecode used by Jazzer relies on [JaCoCo](https://github.com/jacoco/jacoco).
+Previously, Jazzer used AFL-style coverage instrumentation as pioneered by [kelinci](https://github.com/isstac/kelinci).
+
+<p align="center">
+<a href="https://www.code-intelligence.com"><img src="https://www.code-intelligence.com/hubfs/Logos/CI%20Logos/CI_Header_GitHub_quer.jpeg" height=50px alt="Code Intelligence logo"></a>
+</p>
diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel
new file mode 100644
index 00000000..c07582b7
--- /dev/null
+++ b/WORKSPACE.bazel
@@ -0,0 +1,108 @@
+workspace(name = "jazzer")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("//:repositories.bzl", "jazzer_dependencies")
+
+jazzer_dependencies()
+
+load("//:init.bzl", "jazzer_init")
+
+jazzer_init()
+
+http_archive(
+ name = "org_chromium_sysroot_linux_x64",
+ build_file_content = """
+filegroup(
+ name = "sysroot",
+ srcs = glob(["*/**"]),
+ visibility = ["//visibility:public"],
+)
+""",
+ sha256 = "84656a6df544ecef62169cfe3ab6e41bb4346a62d3ba2a045dc5a0a2ecea94a3",
+ urls = ["https://commondatastorage.googleapis.com/chrome-linux-sysroot/toolchain/2202c161310ffde63729f29d27fe7bb24a0bc540/debian_stretch_amd64_sysroot.tar.xz"],
+)
+
+http_archive(
+ name = "com_grail_bazel_toolchain",
+ patches = [
+ # There is no static runtime library for ASan on macOS, so when using
+ # the toolchain in the CI, we have to explicitly depend on the dylib and
+ # add it to the runfiles for clang/ld.
+ "//third_party:bazel-toolchain-export-dynamic-macos-asan.patch",
+ ],
+ sha256 = "da607faed78c4cb5a5637ef74a36fdd2286f85ca5192222c4664efec2d529bb8",
+ strip_prefix = "bazel-toolchain-0.6.3",
+ urls = ["https://github.com/grailbio/bazel-toolchain/archive/0.6.3.tar.gz"],
+)
+
+http_archive(
+ name = "googletest",
+ sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb",
+ strip_prefix = "googletest-release-1.10.0",
+ url = "https://github.com/google/googletest/archive/release-1.10.0.tar.gz",
+)
+
+http_archive(
+ name = "rules_foreign_cc",
+ sha256 = "8ab257584256e2c7eefa0c4e0794ae3be3e8f634f9ec0356da0a653dfed5da9a",
+ strip_prefix = "rules_foreign_cc-76198edc790de8e8514bddaa3895d1145fccd6aa",
+ url = "https://github.com/bazelbuild/rules_foreign_cc/archive/76198edc790de8e8514bddaa3895d1145fccd6aa.tar.gz",
+)
+
+http_archive(
+ name = "rules_jvm_external",
+ sha256 = "f36441aa876c4f6427bfb2d1f2d723b48e9d930b62662bf723ddfb8fc80f0140",
+ strip_prefix = "rules_jvm_external-4.1",
+ url = "https://github.com/bazelbuild/rules_jvm_external/archive/4.1.zip",
+)
+
+http_archive(
+ name = "libjpeg_turbo",
+ build_file = "//third_party:libjpeg_turbo.BUILD",
+ sha256 = "6a965adb02ad898b2ae48214244618fe342baea79db97157fdc70d8844ac6f09",
+ strip_prefix = "libjpeg-turbo-2.0.90",
+ url = "https://github.com/libjpeg-turbo/libjpeg-turbo/archive/2.0.90.tar.gz",
+)
+
+load("@com_grail_bazel_toolchain//toolchain:deps.bzl", "bazel_toolchain_dependencies")
+
+bazel_toolchain_dependencies()
+
+load("@com_grail_bazel_toolchain//toolchain:rules.bzl", "llvm_toolchain")
+
+llvm_toolchain(
+ name = "llvm_toolchain",
+ llvm_version = "13.0.0",
+ sysroot = {
+ "linux-x86_64": "@org_chromium_sysroot_linux_x64//:sysroot",
+ },
+)
+
+load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
+
+rules_jvm_external_deps()
+
+load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
+
+rules_jvm_external_setup()
+
+load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
+
+rules_foreign_cc_dependencies()
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+load("//:maven.bzl", "MAVEN_ARTIFACTS")
+
+maven_install(
+ artifacts = MAVEN_ARTIFACTS,
+ fail_if_repin_required = True,
+ maven_install_json = "//:maven_install.json",
+ repositories = [
+ "https://repo1.maven.org/maven2",
+ ],
+ strict_visibility = True,
+)
+
+load("@maven//:defs.bzl", "pinned_maven_install")
+
+pinned_maven_install()
diff --git a/agent/BUILD.bazel b/agent/BUILD.bazel
new file mode 100644
index 00000000..ddafc246
--- /dev/null
+++ b/agent/BUILD.bazel
@@ -0,0 +1,39 @@
+load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar")
+load("//sanitizers:sanitizers.bzl", "SANITIZER_CLASSES")
+
+java_binary(
+ name = "jazzer_agent_unshaded",
+ create_executable = False,
+ deploy_manifest_lines = [
+ "Premain-Class: com.code_intelligence.jazzer.agent.Agent",
+ "Jazzer-Hook-Classes: {}".format(":".join(SANITIZER_CLASSES)),
+ ],
+ runtime_deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/agent:agent_lib",
+ "//sanitizers",
+ ],
+)
+
+jar_jar(
+ name = "jazzer_agent_deploy",
+ input_jar = "jazzer_agent_unshaded_deploy.jar",
+ rules = "agent_shade_rules",
+ visibility = ["//visibility:public"],
+)
+
+java_binary(
+ name = "jazzer_api",
+ create_executable = False,
+ visibility = ["//visibility:public"],
+ runtime_deps = ["//agent/src/main/java/com/code_intelligence/jazzer/api"],
+)
+
+java_import(
+ name = "jazzer_api_compile_only",
+ jars = [
+ ":jazzer_api_deploy.jar",
+ ],
+ neverlink = True,
+ visibility = ["//visibility:public"],
+ deps = [],
+)
diff --git a/agent/agent_shade_rules b/agent/agent_shade_rules
new file mode 100644
index 00000000..b0e4d8c0
--- /dev/null
+++ b/agent/agent_shade_rules
@@ -0,0 +1,4 @@
+rule kotlin.** com.code_intelligence.jazzer.third_party.kotlin.@1
+rule io.** com.code_intelligence.jazzer.third_party.io.@1
+rule nonapi.** com.code_intelligence.jazzer.third_party.nonapi.@1
+rule net.jodah.** com.code_intelligence.jazzer.third_party.net.jodah.@1
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
new file mode 100644
index 00000000..33d02263
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
@@ -0,0 +1,160 @@
+// Copyright 2021 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.
+
+@file:JvmName("Agent")
+
+package com.code_intelligence.jazzer.agent
+
+import com.code_intelligence.jazzer.instrumentor.CoverageRecorder
+import com.code_intelligence.jazzer.instrumentor.InstrumentationType
+import com.code_intelligence.jazzer.instrumentor.loadHooks
+import com.code_intelligence.jazzer.runtime.ManifestUtils
+import com.code_intelligence.jazzer.utils.ClassNameGlobber
+import java.io.File
+import java.lang.instrument.Instrumentation
+import java.nio.file.Paths
+import java.util.jar.JarFile
+import kotlin.io.path.ExperimentalPathApi
+import kotlin.io.path.exists
+import kotlin.io.path.isDirectory
+
+val KNOWN_ARGUMENTS = listOf(
+ "instrumentation_includes",
+ "instrumentation_excludes",
+ "custom_hook_includes",
+ "custom_hook_excludes",
+ "trace",
+ "custom_hooks",
+ "id_sync_file",
+ "dump_classes_dir",
+)
+
+private object AgentJarFinder {
+ private val agentJarPath = AgentJarFinder::class.java.protectionDomain?.codeSource?.location?.toURI()
+ val agentJarFile = agentJarPath?.let { JarFile(File(it)) }
+}
+
+private val argumentDelimiter = if (System.getProperty("os.name").startsWith("Windows")) ";" else ":"
+
+@OptIn(ExperimentalPathApi::class)
+fun premain(agentArgs: String?, instrumentation: Instrumentation) {
+ // Add the agent jar (i.e., the jar out of which we are currently executing) to the search path of the bootstrap
+ // class loader to ensure that instrumented classes can find the CoverageMap class regardless of which ClassLoader
+ // they are using.
+ if (AgentJarFinder.agentJarFile != null) {
+ instrumentation.appendToBootstrapClassLoaderSearch(AgentJarFinder.agentJarFile)
+ } else {
+ println("WARN: Failed to add agent JAR to bootstrap class loader search path")
+ }
+ val argumentMap = (agentArgs ?: "")
+ .split(',')
+ .mapNotNull {
+ val splitArg = it.split('=', limit = 2)
+ when {
+ splitArg.size != 2 -> {
+ if (splitArg[0].isNotEmpty())
+ println("WARN: Ignoring argument ${splitArg[0]} without value")
+ null
+ }
+ splitArg[0] !in KNOWN_ARGUMENTS -> {
+ println("WARN: Ignoring unknown argument ${splitArg[0]}")
+ null
+ }
+ else -> splitArg[0] to splitArg[1].split(argumentDelimiter)
+ }
+ }.toMap()
+ val manifestCustomHookNames = ManifestUtils.combineManifestValues(ManifestUtils.HOOK_CLASSES).flatMap {
+ it.split(':')
+ }
+ val customHookNames = manifestCustomHookNames + (argumentMap["custom_hooks"] ?: emptyList())
+ val classNameGlobber = ClassNameGlobber(
+ argumentMap["instrumentation_includes"] ?: emptyList(),
+ (argumentMap["instrumentation_excludes"] ?: emptyList()) + customHookNames
+ )
+ CoverageRecorder.classNameGlobber = classNameGlobber
+ val dependencyClassNameGlobber = ClassNameGlobber(
+ argumentMap["custom_hook_includes"] ?: emptyList(),
+ (argumentMap["custom_hook_excludes"] ?: emptyList()) + customHookNames
+ )
+ val instrumentationTypes = (argumentMap["trace"] ?: listOf("all")).flatMap {
+ when (it) {
+ "cmp" -> setOf(InstrumentationType.CMP)
+ "cov" -> setOf(InstrumentationType.COV)
+ "div" -> setOf(InstrumentationType.DIV)
+ "gep" -> setOf(InstrumentationType.GEP)
+ "indir" -> setOf(InstrumentationType.INDIR)
+ "native" -> setOf(InstrumentationType.NATIVE)
+ // Disable GEP instrumentation by default as it appears to negatively affect fuzzing
+ // performance. Our current GEP instrumentation only reports constant indices, but even
+ // when we instead reported non-constant indices, they tended to completely fill up the
+ // table of recent compares and value profile map.
+ "all" -> InstrumentationType.values().toSet() - InstrumentationType.GEP
+ else -> {
+ println("WARN: Skipping unknown instrumentation type $it")
+ emptySet()
+ }
+ }
+ }.toSet()
+ val idSyncFile = argumentMap["id_sync_file"]?.let {
+ Paths.get(it.single()).also { path ->
+ println("INFO: Synchronizing coverage IDs in ${path.toAbsolutePath()}")
+ }
+ }
+ val dumpClassesDir = argumentMap["dump_classes_dir"]?.let {
+ Paths.get(it.single()).toAbsolutePath().also { path ->
+ if (path.exists() && path.isDirectory()) {
+ println("INFO: Dumping instrumented classes into $path")
+ } else {
+ println("ERROR: Cannot dump instrumented classes into $path; does not exist or not a directory")
+ }
+ }
+ }
+ val runtimeInstrumentor = RuntimeInstrumentor(
+ instrumentation,
+ classNameGlobber,
+ dependencyClassNameGlobber,
+ instrumentationTypes,
+ idSyncFile,
+ dumpClassesDir,
+ )
+ instrumentation.apply {
+ addTransformer(runtimeInstrumentor)
+ }
+
+ val relevantClassesLoadedBeforeCustomHooks = instrumentation.allLoadedClasses
+ .map { it.name }
+ .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) }
+ .toSet()
+ val customHooks = customHookNames.toSet().flatMap { hookClassName ->
+ try {
+ loadHooks(Class.forName(hookClassName)).also {
+ println("INFO: Loaded ${it.size} hooks from $hookClassName")
+ }
+ } catch (_: ClassNotFoundException) {
+ println("WARN: Failed to load hooks from $hookClassName")
+ emptySet()
+ }
+ }
+ val relevantClassesLoadedAfterCustomHooks = instrumentation.allLoadedClasses
+ .map { it.name }
+ .filter { classNameGlobber.includes(it) || dependencyClassNameGlobber.includes(it) }
+ .toSet()
+ val nonHookClassesLoadedByHooks = relevantClassesLoadedAfterCustomHooks - relevantClassesLoadedBeforeCustomHooks
+ if (nonHookClassesLoadedByHooks.isNotEmpty()) {
+ println("WARN: Hooks were not applied to the following classes as they are dependencies of hooks:")
+ println("WARN: ${nonHookClassesLoadedByHooks.joinToString()}")
+ }
+
+ runtimeInstrumentor.registerCustomHooks(customHooks)
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel
new file mode 100644
index 00000000..2d5eec5c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+kt_jvm_library(
+ name = "agent_lib",
+ srcs = [
+ "Agent.kt",
+ "CoverageIdStrategy.kt",
+ "RuntimeInstrumentor.kt",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor",
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime",
+ ],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt
new file mode 100644
index 00000000..fd2a1e7c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/CoverageIdStrategy.kt
@@ -0,0 +1,201 @@
+// Copyright 2021 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.agent
+
+import java.nio.ByteBuffer
+import java.nio.channels.FileChannel
+import java.nio.channels.FileLock
+import java.nio.file.Path
+import java.nio.file.StandardOpenOption
+import java.util.UUID
+
+/**
+ * Indicates a fatal failure to generate synchronized coverage IDs.
+ */
+internal class CoverageIdException(cause: Throwable? = null) :
+ RuntimeException("Failed to synchronize coverage IDs", cause)
+
+interface CoverageIdStrategy {
+ /**
+ * Obtain the first coverage ID to be used for the class [className].
+ * The caller *must* also call [commitIdCount] once it has instrumented that class, even if instrumentation fails.
+ */
+ @Throws(CoverageIdException::class)
+ fun obtainFirstId(className: String): Int
+
+ /**
+ * Records the number of coverage IDs used to instrument the class specified in a previous call to [obtainFirstId].
+ * If instrumenting the class should fail, this function must still be called. In this case, [idCount] is set to 0.
+ */
+ @Throws(CoverageIdException::class)
+ fun commitIdCount(idCount: Int)
+}
+
+/**
+ * An unsynchronized strategy for coverage ID generation that simply increments a global counter.
+ */
+internal class TrivialCoverageIdStrategy : CoverageIdStrategy {
+ private var nextEdgeId = 0
+
+ override fun obtainFirstId(className: String) = nextEdgeId
+
+ override fun commitIdCount(idCount: Int) {
+ nextEdgeId += idCount
+ }
+}
+
+/**
+ * Reads the [FileChannel] to the end as a UTF-8 string.
+ */
+private fun FileChannel.readFully(): String {
+ check(size() <= Int.MAX_VALUE)
+ val buffer = ByteBuffer.allocate(size().toInt())
+ while (buffer.hasRemaining()) {
+ when (read(buffer)) {
+ 0 -> throw IllegalStateException("No bytes read")
+ -1 -> break
+ }
+ }
+ return String(buffer.array())
+}
+
+/**
+ * Appends [string] to the end of the [FileChannel].
+ */
+private fun FileChannel.append(string: String) {
+ position(size())
+ write(ByteBuffer.wrap(string.toByteArray()))
+}
+
+/**
+ * A strategy for coverage ID generation that synchronizes the IDs assigned to a class with other processes via the
+ * specified [idSyncFile].
+ * This class takes care of synchronizing the access to the file between multiple processes as long as the general
+ * contract of [CoverageIdStrategy] is followed.
+ *
+ * Rationale: Coverage (i.e., edge) IDs differ from other kinds of IDs, such as those generated for call sites or cmp
+ * instructions, in that they should be consecutive, collision-free, and lie in a known, small range. This precludes us
+ * from generating them simply as hashes of class names and explains why go through the arduous process of synchronizing
+ * them across multiple agents.
+ */
+internal class SynchronizedCoverageIdStrategy(private val idSyncFile: Path) : CoverageIdStrategy {
+ val uuid: UUID = UUID.randomUUID()
+ var idFileLock: FileLock? = null
+
+ var cachedFirstId: Int? = null
+ var cachedClassName: String? = null
+ var cachedIdCount: Int? = null
+
+ /**
+ * Obtains a coverage ID for [className] such that all cooperating agent processes will obtain the same ID.
+ * There are two cases to consider:
+ * - This agent process is the first to encounter [className], i.e., it does not find a record for that class in
+ * [idSyncFile]. In this case, a lock on the file is held until the class has been instrumented and a record with
+ * the required number of coverage IDs has been added.
+ * - Another agent process has already encountered [className], i.e., there is a record that class in [idSyncFile].
+ * In this case, the lock on the file is returned immediately and the extracted first coverage ID is returned to
+ * the caller. The caller is still expected to call [commitIdCount] so that desynchronization can be detected.
+ */
+ override fun obtainFirstId(className: String): Int {
+ try {
+ check(idFileLock == null) { "Already holding a lock on the ID file" }
+ val localIdFile = FileChannel.open(
+ idSyncFile,
+ StandardOpenOption.WRITE,
+ StandardOpenOption.READ
+ )
+ // Wait until we have obtained the lock on the sync file. We hold the lock from this point until we have
+ // finished reading and writing (if necessary) to the file.
+ val localIdFileLock = localIdFile.lock()
+ check(localIdFileLock.isValid && !localIdFileLock.isShared)
+ // Parse the sync file, which consists of lines of the form
+ // <class name>:<first ID>:<num IDs>
+ val idInfo = localIdFileLock.channel().readFully()
+ .lineSequence()
+ .filterNot { it.isBlank() }
+ .map { line ->
+ val parts = line.split(':')
+ check(parts.size == 4) {
+ "Expected ID file line to be of the form '<class name>:<first ID>:<num IDs>:<uuid>', got '$line'"
+ }
+ val lineClassName = parts[0]
+ val lineFirstId = parts[1].toInt()
+ check(lineFirstId >= 0) { "Negative first ID in line: $line" }
+ val lineIdCount = parts[2].toInt()
+ check(lineIdCount >= 0) { "Negative ID count in line: $line" }
+ Triple(lineClassName, lineFirstId, lineIdCount)
+ }.toList()
+ cachedClassName = className
+ val idInfoForClass = idInfo.filter { it.first == className }
+ return when (idInfoForClass.size) {
+ 0 -> {
+ // We are the first to encounter this class and thus need to hold the lock until the class has been
+ // instrumented and we know the required number of coverage IDs.
+ idFileLock = localIdFileLock
+ // Compute the next free ID as the maximum over the sums of first ID and ID count, starting at 0 if
+ // this is the first ID to be assigned. In fact, since this is the only way new lines are added to
+ // the file, the maximum is always attained by the last line.
+ val nextFreeId = idInfo.asSequence().map { it.second + it.third }.lastOrNull() ?: 0
+ cachedFirstId = nextFreeId
+ nextFreeId
+ }
+ 1 -> {
+ // This class has already been instrumented elsewhere, so we just return the first ID and ID count
+ // reported from there and release the lock right away. The caller is still expected to call
+ // commitIdCount.
+ localIdFile.close()
+ cachedIdCount = idInfoForClass.single().third
+ idInfoForClass.single().second
+ }
+ else -> {
+ localIdFile.close()
+ System.err.println(idInfo.joinToString("\n") { "${it.first}:${it.second}:${it.third}" })
+ throw IllegalStateException("Multiple entries for $className in ID file")
+ }
+ }
+ } catch (e: Exception) {
+ throw CoverageIdException(e)
+ }
+ }
+
+ override fun commitIdCount(idCount: Int) {
+ val localIdFileLock = idFileLock
+ try {
+ check(cachedClassName != null)
+ if (localIdFileLock == null) {
+ // We released the lock already in obtainFirstId since the class had already been instrumented
+ // elsewhere. As we know the expected number of IDs for the current class in this case, check for
+ // deviations.
+ check(cachedIdCount != null)
+ check(idCount == cachedIdCount) {
+ "$cachedClassName has $idCount edges, but $cachedIdCount edges reserved in ID file"
+ }
+ } else {
+ // We are the first to instrument this class and should record the number of IDs in the sync file.
+ check(cachedFirstId != null)
+ localIdFileLock.channel().append("$cachedClassName:$cachedFirstId:$idCount:$uuid\n")
+ localIdFileLock.channel().force(true)
+ }
+ idFileLock = null
+ cachedFirstId = null
+ cachedIdCount = null
+ cachedClassName = null
+ } catch (e: Exception) {
+ throw CoverageIdException(e)
+ } finally {
+ localIdFileLock?.channel()?.close()
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
new file mode 100644
index 00000000..e2283aa2
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/agent/RuntimeInstrumentor.kt
@@ -0,0 +1,182 @@
+// Copyright 2021 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.agent
+
+import com.code_intelligence.jazzer.instrumentor.ClassInstrumentor
+import com.code_intelligence.jazzer.instrumentor.CoverageRecorder
+import com.code_intelligence.jazzer.instrumentor.Hook
+import com.code_intelligence.jazzer.instrumentor.InstrumentationType
+import com.code_intelligence.jazzer.instrumentor.loadHooks
+import com.code_intelligence.jazzer.runtime.NativeLibHooks
+import com.code_intelligence.jazzer.runtime.TraceCmpHooks
+import com.code_intelligence.jazzer.runtime.TraceDivHooks
+import com.code_intelligence.jazzer.runtime.TraceIndirHooks
+import com.code_intelligence.jazzer.utils.ClassNameGlobber
+import java.lang.instrument.ClassFileTransformer
+import java.lang.instrument.Instrumentation
+import java.nio.file.Path
+import java.security.ProtectionDomain
+import kotlin.math.roundToInt
+import kotlin.system.exitProcess
+import kotlin.time.measureTimedValue
+
+internal class RuntimeInstrumentor(
+ private val instrumentation: Instrumentation,
+ private val classesToInstrument: ClassNameGlobber,
+ private val dependencyClassesToInstrument: ClassNameGlobber,
+ private val instrumentationTypes: Set<InstrumentationType>,
+ idSyncFile: Path?,
+ private val dumpClassesDir: Path?,
+) : ClassFileTransformer {
+
+ private val coverageIdSynchronizer = if (idSyncFile != null)
+ SynchronizedCoverageIdStrategy(idSyncFile)
+ else
+ TrivialCoverageIdStrategy()
+
+ private val includedHooks = instrumentationTypes
+ .mapNotNull { type ->
+ when (type) {
+ InstrumentationType.CMP -> TraceCmpHooks::class.java
+ InstrumentationType.DIV -> TraceDivHooks::class.java
+ InstrumentationType.INDIR -> TraceIndirHooks::class.java
+ InstrumentationType.NATIVE -> NativeLibHooks::class.java
+ else -> null
+ }
+ }
+ .flatMap { loadHooks(it) }
+ private val customHooks = emptyList<Hook>().toMutableList()
+
+ fun registerCustomHooks(hooks: List<Hook>) {
+ customHooks.addAll(hooks)
+ }
+
+ @OptIn(kotlin.time.ExperimentalTime::class)
+ override fun transform(
+ loader: ClassLoader?,
+ internalClassName: String,
+ classBeingRedefined: Class<*>?,
+ protectionDomain: ProtectionDomain?,
+ classfileBuffer: ByteArray,
+ ): ByteArray? {
+ return try {
+ // Bail out early if we would instrument ourselves. This prevents ClassCircularityErrors as we might need to
+ // load additional Jazzer classes until we reach the full exclusion logic.
+ if (internalClassName.startsWith("com/code_intelligence/jazzer/"))
+ return null
+ transformInternal(internalClassName, classfileBuffer)
+ } catch (t: Throwable) {
+ // Throwables raised from transform are silently dropped, making it extremely hard to detect instrumentation
+ // failures. The docs advise to use a top-level try-catch.
+ // https://docs.oracle.com/javase/9/docs/api/java/lang/instrument/ClassFileTransformer.html
+ t.printStackTrace()
+ throw t
+ }.also { instrumentedByteCode ->
+ // Only dump classes that were instrumented.
+ if (instrumentedByteCode != null && dumpClassesDir != null) {
+ val relativePath = "$internalClassName.class"
+ val absolutePath = dumpClassesDir.resolve(relativePath)
+ val dumpFile = absolutePath.toFile()
+ dumpFile.parentFile.mkdirs()
+ dumpFile.writeBytes(instrumentedByteCode)
+ }
+ }
+ }
+
+ override fun transform(
+ module: Module?,
+ loader: ClassLoader?,
+ internalClassName: String,
+ classBeingRedefined: Class<*>?,
+ protectionDomain: ProtectionDomain?,
+ classfileBuffer: ByteArray
+ ): ByteArray? {
+ if (module != null && !module.canRead(RuntimeInstrumentor::class.java.module)) {
+ // Make all other modules read our (unnamed) module, which allows them to access the classes needed by the
+ // instrumentations, e.g. CoverageMap. If a module can't be modified, it should not be instrumented as the
+ // injected bytecode might throw NoClassDefFoundError.
+ // https://mail.openjdk.java.net/pipermail/jigsaw-dev/2021-May/014663.html
+ if (!instrumentation.isModifiableModule(module)) {
+ val prettyClassName = internalClassName.replace('/', '.')
+ println("WARN: Failed to instrument $prettyClassName in unmodifiable module ${module.name}, skipping")
+ return null
+ }
+ instrumentation.redefineModule(
+ module,
+ /* extraReads */ setOf(RuntimeInstrumentor::class.java.module),
+ emptyMap(),
+ emptyMap(),
+ emptySet(),
+ emptyMap()
+ )
+ }
+ return transform(loader, internalClassName, classBeingRedefined, protectionDomain, classfileBuffer)
+ }
+
+ @OptIn(kotlin.time.ExperimentalTime::class)
+ fun transformInternal(internalClassName: String, classfileBuffer: ByteArray): ByteArray? {
+ val fullInstrumentation = when {
+ classesToInstrument.includes(internalClassName) -> true
+ dependencyClassesToInstrument.includes(internalClassName) -> false
+ else -> return null
+ }
+ val prettyClassName = internalClassName.replace('/', '.')
+ val (instrumentedBytecode, duration) = measureTimedValue {
+ try {
+ instrument(internalClassName, classfileBuffer, fullInstrumentation)
+ } catch (e: CoverageIdException) {
+ System.err.println("ERROR: Coverage IDs are out of sync")
+ e.printStackTrace()
+ exitProcess(1)
+ } catch (e: Exception) {
+ println("WARN: Failed to instrument $prettyClassName, skipping")
+ e.printStackTrace()
+ return null
+ }
+ }
+ val durationInMs = duration.inWholeMilliseconds
+ val sizeIncrease = ((100.0 * (instrumentedBytecode.size - classfileBuffer.size)) / classfileBuffer.size).roundToInt()
+ if (fullInstrumentation) {
+ println("INFO: Instrumented $prettyClassName (took $durationInMs ms, size +$sizeIncrease%)")
+ } else {
+ println("INFO: Instrumented $prettyClassName with custom hooks only (took $durationInMs ms, size +$sizeIncrease%)")
+ }
+ return instrumentedBytecode
+ }
+
+ private fun instrument(internalClassName: String, bytecode: ByteArray, fullInstrumentation: Boolean): ByteArray {
+ return ClassInstrumentor(bytecode).run {
+ if (fullInstrumentation) {
+ // Hook instrumentation must be performed after data flow tracing as the injected
+ // bytecode would trigger the GEP callbacks for byte[]. Coverage instrumentation
+ // must be performed after hook instrumentation as the injected bytecode would
+ // trigger the GEP callbacks for ByteBuffer.
+ traceDataFlow(instrumentationTypes)
+ hooks(includedHooks + customHooks)
+ val firstId = coverageIdSynchronizer.obtainFirstId(internalClassName)
+ var actualNumEdgeIds = 0
+ try {
+ actualNumEdgeIds = coverage(firstId)
+ } finally {
+ coverageIdSynchronizer.commitIdCount(actualNumEdgeIds)
+ }
+ CoverageRecorder.recordInstrumentedClass(internalClassName, bytecode, firstId, firstId + actualNumEdgeIds)
+ } else {
+ hooks(customHooks)
+ }
+ instrumentedBytecode
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java
new file mode 100644
index 00000000..93340ee8
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzConstructionException.java
@@ -0,0 +1,32 @@
+// Copyright 2021 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.api;
+
+// An exception wrapping a Throwable thrown during the construction of parameters for, but not the
+// actual invocation of an autofuzzed method.
+/**
+ * Only used internally.
+ */
+public class AutofuzzConstructionException extends RuntimeException {
+ public AutofuzzConstructionException() {
+ super();
+ }
+ public AutofuzzConstructionException(String message) {
+ super(message);
+ }
+ public AutofuzzConstructionException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java
new file mode 100644
index 00000000..7e6203ce
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/AutofuzzInvocationException.java
@@ -0,0 +1,26 @@
+// Copyright 2021 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.api;
+
+// An exception wrapping a {@link Throwable} thrown during the actual invocation of, but not the
+// construction of parameters for an autofuzzed method.
+/**
+ * Only used internally.
+ */
+public class AutofuzzInvocationException extends RuntimeException {
+ public AutofuzzInvocationException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel
new file mode 100644
index 00000000..e573e757
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/BUILD.bazel
@@ -0,0 +1,28 @@
+java_library(
+ name = "api",
+ srcs = [
+ "AutofuzzConstructionException.java",
+ "AutofuzzInvocationException.java",
+ "CannedFuzzedDataProvider.java",
+ "Consumer1.java",
+ "Consumer2.java",
+ "Consumer3.java",
+ "Consumer4.java",
+ "Consumer5.java",
+ "Function1.java",
+ "Function2.java",
+ "Function3.java",
+ "Function4.java",
+ "Function5.java",
+ "FuzzedDataProvider.java",
+ "FuzzerSecurityIssueCritical.java",
+ "FuzzerSecurityIssueHigh.java",
+ "FuzzerSecurityIssueLow.java",
+ "FuzzerSecurityIssueMedium.java",
+ "HookType.java",
+ "Jazzer.java",
+ "MethodHook.java",
+ "MethodHooks.java",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java
new file mode 100644
index 00000000..7209a497
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/CannedFuzzedDataProvider.java
@@ -0,0 +1,211 @@
+// Copyright 2021 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.api;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Replays recorded FuzzedDataProvider invocations that were executed while fuzzing.
+ * Note: This class is only meant to be used by Jazzer's generated reproducers.
+ */
+final public class CannedFuzzedDataProvider implements FuzzedDataProvider {
+ private final Iterator<Object> nextReply;
+
+ public CannedFuzzedDataProvider(String can) {
+ byte[] rawIn = Base64.getDecoder().decode(can);
+ ArrayList<Object> recordedReplies;
+ try (ByteArrayInputStream byteStream = new ByteArrayInputStream(rawIn)) {
+ try (ObjectInputStream objectStream = new ObjectInputStream(byteStream)) {
+ recordedReplies = (ArrayList<Object>) objectStream.readObject();
+ }
+ } catch (IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ nextReply = recordedReplies.iterator();
+ }
+
+ public static CannedFuzzedDataProvider create(List<Object> objects) {
+ try {
+ try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
+ try (ObjectOutputStream out = new ObjectOutputStream(bout)) {
+ out.writeObject(new ArrayList<>(objects));
+ String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
+ return new CannedFuzzedDataProvider(base64);
+ }
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public boolean consumeBoolean() {
+ return (boolean) nextReply.next();
+ }
+
+ @Override
+ public boolean[] consumeBooleans(int maxLength) {
+ return (boolean[]) nextReply.next();
+ }
+
+ @Override
+ public byte consumeByte() {
+ return (byte) nextReply.next();
+ }
+
+ @Override
+ public byte consumeByte(byte min, byte max) {
+ return (byte) nextReply.next();
+ }
+
+ @Override
+ public short consumeShort() {
+ return (short) nextReply.next();
+ }
+
+ @Override
+ public short consumeShort(short min, short max) {
+ return (short) nextReply.next();
+ }
+
+ @Override
+ public short[] consumeShorts(int maxLength) {
+ return (short[]) nextReply.next();
+ }
+
+ @Override
+ public int consumeInt() {
+ return (int) nextReply.next();
+ }
+
+ @Override
+ public int consumeInt(int min, int max) {
+ return (int) nextReply.next();
+ }
+
+ @Override
+ public int[] consumeInts(int maxLength) {
+ return (int[]) nextReply.next();
+ }
+
+ @Override
+ public long consumeLong() {
+ return (long) nextReply.next();
+ }
+
+ @Override
+ public long consumeLong(long min, long max) {
+ return (long) nextReply.next();
+ }
+
+ @Override
+ public long[] consumeLongs(int maxLength) {
+ return (long[]) nextReply.next();
+ }
+
+ @Override
+ public float consumeFloat() {
+ return (float) nextReply.next();
+ }
+
+ @Override
+ public float consumeRegularFloat() {
+ return (float) nextReply.next();
+ }
+
+ @Override
+ public float consumeRegularFloat(float min, float max) {
+ return (float) nextReply.next();
+ }
+
+ @Override
+ public float consumeProbabilityFloat() {
+ return (float) nextReply.next();
+ }
+
+ @Override
+ public double consumeDouble() {
+ return (double) nextReply.next();
+ }
+
+ @Override
+ public double consumeRegularDouble(double min, double max) {
+ return (double) nextReply.next();
+ }
+
+ @Override
+ public double consumeRegularDouble() {
+ return (double) nextReply.next();
+ }
+
+ @Override
+ public double consumeProbabilityDouble() {
+ return (double) nextReply.next();
+ }
+
+ @Override
+ public char consumeChar() {
+ return (char) nextReply.next();
+ }
+
+ @Override
+ public char consumeChar(char min, char max) {
+ return (char) nextReply.next();
+ }
+
+ @Override
+ public char consumeCharNoSurrogates() {
+ return (char) nextReply.next();
+ }
+
+ @Override
+ public String consumeAsciiString(int maxLength) {
+ return (String) nextReply.next();
+ }
+
+ @Override
+ public String consumeString(int maxLength) {
+ return (String) nextReply.next();
+ }
+
+ @Override
+ public String consumeRemainingAsAsciiString() {
+ return (String) nextReply.next();
+ }
+
+ @Override
+ public String consumeRemainingAsString() {
+ return (String) nextReply.next();
+ }
+
+ @Override
+ public byte[] consumeBytes(int maxLength) {
+ return (byte[]) nextReply.next();
+ }
+
+ @Override
+ public byte[] consumeRemainingAsBytes() {
+ return (byte[]) nextReply.next();
+ }
+
+ @Override
+ public int remainingBytes() {
+ return (int) nextReply.next();
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java
new file mode 100644
index 00000000..472c2efd
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer1.java
@@ -0,0 +1,22 @@
+// Copyright 2021 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.api;
+
+import java.util.function.Consumer;
+
+@FunctionalInterface
+public interface Consumer1<T1> extends Consumer<T1> {
+ @Override void accept(T1 t1);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java
new file mode 100644
index 00000000..d951ade7
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer2.java
@@ -0,0 +1,22 @@
+// Copyright 2021 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.api;
+
+import java.util.function.BiConsumer;
+
+@FunctionalInterface
+public interface Consumer2<T1, T2> extends BiConsumer<T1, T2> {
+ @Override void accept(T1 t1, T2 t2);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java
new file mode 100644
index 00000000..c508fe53
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer3.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Consumer3<T1, T2, T3> {
+ void accept(T1 t1, T2 t2, T3 t3);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java
new file mode 100644
index 00000000..6ee70141
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer4.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Consumer4<T1, T2, T3, T4> {
+ void accept(T1 t1, T2 t2, T3 t3, T4 t4);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java
new file mode 100644
index 00000000..523df53c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Consumer5.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Consumer5<T1, T2, T3, T4, T5> {
+ void accept(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java
new file mode 100644
index 00000000..43d68cc7
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Function1.java
@@ -0,0 +1,22 @@
+// Copyright 2021 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.api;
+
+import java.util.function.Function;
+
+@FunctionalInterface
+public interface Function1<T1, R> extends Function<T1, R> {
+ @Override R apply(T1 t1);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java
new file mode 100644
index 00000000..6e733b1c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Function2.java
@@ -0,0 +1,22 @@
+// Copyright 2021 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.api;
+
+import java.util.function.BiFunction;
+
+@FunctionalInterface
+public interface Function2<T1, T2, R> extends BiFunction<T1, T2, R> {
+ @Override R apply(T1 t1, T2 t2);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java
new file mode 100644
index 00000000..07d593f9
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Function3.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Function3<T1, T2, T3, R> {
+ R apply(T1 t1, T2 t2, T3 t3);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java
new file mode 100644
index 00000000..0e6ec75e
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Function4.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Function4<T1, T2, T3, T4, R> {
+ R apply(T1 t1, T2 t2, T3 t3, T4 t4);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java
new file mode 100644
index 00000000..cd833f78
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Function5.java
@@ -0,0 +1,20 @@
+// Copyright 2021 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.api;
+
+@FunctionalInterface
+public interface Function5<T1, T2, T3, T4, T5, R> {
+ R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java
new file mode 100644
index 00000000..b1f38b50
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzedDataProvider.java
@@ -0,0 +1,444 @@
+// Copyright 2021 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.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A convenience wrapper turning the raw fuzzer input bytes into Java primitive types.
+ *
+ * <p>The methods defined by this interface behave similarly to {@link Random#nextInt()}, with all
+ * returned values depending deterministically on the fuzzer input for the current run.
+ */
+public interface FuzzedDataProvider {
+ /**
+ * Consumes a {@code boolean} from the fuzzer input.
+ *
+ * @return a {@code boolean}
+ */
+ boolean consumeBoolean();
+
+ /**
+ * Consumes a {@code boolean} array from the fuzzer input.
+ * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input
+ * is not sufficiently long.
+ *
+ * @param maxLength the maximum length of the array
+ * @return a {@code boolean} array of length at most {@code length}
+ */
+ boolean[] consumeBooleans(int maxLength);
+
+ /**
+ * Consumes a {@code byte} from the fuzzer input.
+ *
+ * @return a {@code byte}
+ */
+ byte consumeByte();
+
+ /**
+ * Consumes a {@code byte} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @param min the inclusive lower bound on the returned value
+ * @param max the inclusive upper bound on the returned value
+ * @return a {@code byte} in the range {@code [min, max]}
+ */
+ byte consumeByte(byte min, byte max);
+
+ /**
+ * Consumes a {@code byte} array from the fuzzer input.
+ * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input
+ * is not sufficiently long.
+ *
+ * @param maxLength the maximum length of the array
+ * @return a {@code byte} array of length at most {@code length}
+ */
+ byte[] consumeBytes(int maxLength);
+
+ /**
+ * Consumes the remaining fuzzer input as a {@code byte} array.
+ * <p><b>Note:</b> After calling this method, further calls to methods of this interface will
+ * return fixed values only.
+ *
+ * @return a {@code byte} array
+ */
+ byte[] consumeRemainingAsBytes();
+
+ /**
+ * Consumes a {@code short} from the fuzzer input.
+ *
+ * @return a {@code short}
+ */
+ short consumeShort();
+
+ /**
+ * Consumes a {@code short} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @param min the inclusive lower bound on the returned value
+ * @param max the inclusive upper bound on the returned value
+ * @return a {@code short} in the range {@code [min, max]}
+ */
+ short consumeShort(short min, short max);
+
+ /**
+ * Consumes a {@code short} array from the fuzzer input.
+ * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input
+ * is not sufficiently long.
+ *
+ * @param maxLength the maximum length of the array
+ * @return a {@code short} array of length at most {@code length}
+ */
+ short[] consumeShorts(int maxLength);
+
+ /**
+ * Consumes an {@code int} from the fuzzer input.
+ *
+ * @return an {@code int}
+ */
+ int consumeInt();
+
+ /**
+ * Consumes an {@code int} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @param min the inclusive lower bound on the returned value
+ * @param max the inclusive upper bound on the returned value
+ * @return an {@code int} in the range {@code [min, max]}
+ */
+ int consumeInt(int min, int max);
+
+ /**
+ * Consumes an {@code int} array from the fuzzer input.
+ * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input
+ * is not sufficiently long.
+ *
+ * @param maxLength the maximum length of the array
+ * @return an {@code int} array of length at most {@code length}
+ */
+ int[] consumeInts(int maxLength);
+
+ /**
+ * Consumes a {@code long} from the fuzzer input.
+ *
+ * @return a {@code long}
+ */
+ long consumeLong();
+
+ /**
+ * Consumes a {@code long} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @param min the inclusive lower bound on the returned value
+ * @param max the inclusive upper bound on the returned value
+ * @return a {@code long} in the range @{code [min, max]}
+ */
+ long consumeLong(long min, long max);
+
+ /**
+ * Consumes a {@code long} array from the fuzzer input.
+ * <p>The array will usually have length {@code length}, but might be shorter if the fuzzer input
+ * is not sufficiently long.
+ *
+ * @param maxLength the maximum length of the array
+ * @return a {@code long} array of length at most {@code length}
+ */
+ long[] consumeLongs(int maxLength);
+
+ /**
+ * Consumes a {@code float} from the fuzzer input.
+ *
+ * @return a {@code float} that may have a special value (e.g. a NaN or infinity)
+ */
+ float consumeFloat();
+
+ /**
+ * Consumes a regular {@code float} from the fuzzer input.
+ *
+ * @return a {@code float} that is not a special value (e.g. not a NaN or infinity)
+ */
+ float consumeRegularFloat();
+
+ /**
+ * Consumes a regular {@code float} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @return a {@code float} in the range {@code [min, max]}
+ */
+ float consumeRegularFloat(float min, float max);
+
+ /**
+ * Consumes a {@code float} between 0.0 and 1.0 (inclusive) from the fuzzer input.
+ *
+ * @return a {@code float} in the range {@code [0.0, 1.0]}
+ */
+ float consumeProbabilityFloat();
+
+ /**
+ * Consumes a {@code double} from the fuzzer input.
+ *
+ * @return a {@code double} that may have a special value (e.g. a NaN or infinity)
+ */
+ double consumeDouble();
+
+ /**
+ * Consumes a regular {@code double} from the fuzzer input.
+ *
+ * @return a {@code double} that is not a special value (e.g. not a NaN or infinity)
+ */
+ double consumeRegularDouble();
+
+ /**
+ * Consumes a regular {@code double} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @return a {@code double} in the range {@code [min, max]}
+ */
+ double consumeRegularDouble(double min, double max);
+
+ /**
+ * Consumes a {@code double} between 0.0 and 1.0 (inclusive) from the fuzzer input.
+ *
+ * @return a {@code double} in the range {@code [0.0, 1.0]}
+ */
+ double consumeProbabilityDouble();
+
+ /**
+ * Consumes a {@code char} from the fuzzer input.
+ */
+ char consumeChar();
+
+ /**
+ * Consumes a {@code char} between {@code min} and {@code max} from the fuzzer input.
+ *
+ * @param min the inclusive lower bound on the returned value
+ * @param max the inclusive upper bound on the returned value
+ * @return a {@code char} in the range {@code [min, max]}
+ */
+ char consumeChar(char min, char max);
+
+ /**
+ * Consumes a {@code char} from the fuzzer input that is never a UTF-16 surrogate character.
+ */
+ char consumeCharNoSurrogates();
+
+ /**
+ * Consumes a {@link String} from the fuzzer input.
+ * <p>The returned string may be of any length between 0 and {@code maxLength}, even if there is
+ * more fuzzer input available.
+ *
+ * @param maxLength the maximum length of the string
+ * @return a {@link String} of length between 0 and {@code maxLength} (inclusive)
+ */
+ String consumeString(int maxLength);
+
+ /**
+ * Consumes the remaining fuzzer input as a {@link String}.
+ * <p><b>Note:</b> After calling this method, further calls to methods of this interface will
+ * return fixed values only.
+ *
+ * @return a {@link String}
+ */
+ String consumeRemainingAsString();
+
+ /**
+ * Consumes an ASCII-only {@link String} from the fuzzer input.
+ * <p>The returned string may be of any length between 0 and {@code maxLength}, even if there is
+ * more fuzzer input available.
+ *
+ * @param maxLength the maximum length of the string
+ * @return a {@link String} of length between 0 and {@code maxLength} (inclusive) that contains
+ * only ASCII characters
+ */
+ String consumeAsciiString(int maxLength);
+
+ /**
+ * Consumes the remaining fuzzer input as an ASCII-only {@link String}.
+ * <p><b>Note:</b> After calling this method, further calls to methods of this interface will
+ * return fixed values only.
+ *
+ * @return a {@link String} that contains only ASCII characters
+ */
+ String consumeRemainingAsAsciiString();
+
+ /**
+ * Returns the number of unconsumed bytes in the fuzzer input.
+ *
+ * @return the number of unconsumed bytes in the fuzzer input
+ */
+ int remainingBytes();
+
+ /**
+ * Picks an element from {@code collection} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param collection the {@link Collection} to pick an element from.
+ * @param <T> the type of the collection element
+ * @return an element from {@code collection} chosen based on the fuzzer input
+ */
+ @SuppressWarnings("unchecked")
+ default<T> T pickValue(Collection<T> collection) {
+ int size = collection.size();
+ if (size == 0) {
+ throw new IllegalArgumentException("collection is empty");
+ }
+ if (collection instanceof List<?>) {
+ return ((List<T>) collection).get(consumeInt(0, size - 1));
+ } else {
+ return (T) pickValue(collection.toArray());
+ }
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @param <T> the type of the array element
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default<T> T pickValue(T[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default boolean pickValue(boolean[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default byte pickValue(byte[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default short pickValue(short[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default int pickValue(int[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default long pickValue(long[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default double pickValue(double[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default float pickValue(float[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks an element from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @return an element from {@code array} chosen based on the fuzzer input
+ */
+ default char pickValue(char[] array) {
+ return array[consumeInt(0, array.length - 1)];
+ }
+
+ /**
+ * Picks {@code numOfElements} elements from {@code collection} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param collection the {@link Collection} to pick an element from.
+ * @param numOfElements the number of elements to pick.
+ * @param <T> the type of the collection element
+ * @return an array of size {@code numOfElements} from {@code collection} chosen based on the
+ * fuzzer input
+ */
+ default<T> List<T> pickValues(Collection<T> collection, int numOfElements) {
+ int size = collection.size();
+ if (size == 0) {
+ throw new IllegalArgumentException("collection is empty");
+ }
+ if (numOfElements > collection.size()) {
+ throw new IllegalArgumentException("numOfElements exceeds collection.size()");
+ }
+
+ List<T> remainingElements = new ArrayList<>(collection);
+ List<T> pickedElements = new ArrayList<>();
+ for (int i = 0; i < numOfElements; i++) {
+ T element = pickValue(remainingElements);
+ pickedElements.add(element);
+ remainingElements.remove(element);
+ }
+ return pickedElements;
+ }
+
+ /**
+ * Picks {@code numOfElements} elements from {@code array} based on the fuzzer input.
+ * <p><b>Note:</b> The distribution of picks is not perfectly uniform.
+ *
+ * @param array the array to pick an element from.
+ * @param numOfElements the number of elements to pick.
+ * @param <T> the type of the array element
+ * @return an array of size {@code numOfElements} from {@code array} chosen based on the fuzzer
+ * input
+ */
+ default<T> List<T> pickValues(T[] array, int numOfElements) {
+ return pickValues(Arrays.asList(array), numOfElements);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java
new file mode 100644
index 00000000..4402a7f3
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueCritical.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.api;
+
+/**
+ * Thrown to indicate that a fuzz target has detected a critical severity security issue rather than
+ * a normal bug.
+ *
+ * There is only a semantical but no functional difference between throwing exceptions of this type
+ * or any other. However, automated fuzzing platforms can use the extra information to handle the
+ * detected issues appropriately.
+ */
+public class FuzzerSecurityIssueCritical extends RuntimeException {
+ public FuzzerSecurityIssueCritical() {}
+
+ public FuzzerSecurityIssueCritical(String message) {
+ super(message);
+ }
+
+ public FuzzerSecurityIssueCritical(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FuzzerSecurityIssueCritical(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java
new file mode 100644
index 00000000..4d323e56
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueHigh.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.api;
+
+/**
+ * Thrown to indicate that a fuzz target has detected a high severity security issue rather than a
+ * normal bug.
+ *
+ * There is only a semantical but no functional difference between throwing exceptions of this type
+ * or any other. However, automated fuzzing platforms can use the extra information to handle the
+ * detected issues appropriately.
+ */
+public class FuzzerSecurityIssueHigh extends RuntimeException {
+ public FuzzerSecurityIssueHigh() {}
+
+ public FuzzerSecurityIssueHigh(String message) {
+ super(message);
+ }
+
+ public FuzzerSecurityIssueHigh(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FuzzerSecurityIssueHigh(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java
new file mode 100644
index 00000000..364b3afb
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueLow.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.api;
+
+/**
+ * Thrown to indicate that a fuzz target has detected a low severity security issue rather than a
+ * normal bug.
+ *
+ * There is only a semantical but no functional difference between throwing exceptions of this type
+ * or any other. However, automated fuzzing platforms can use the extra information to handle the
+ * detected issues appropriately.
+ */
+public class FuzzerSecurityIssueLow extends RuntimeException {
+ public FuzzerSecurityIssueLow() {}
+
+ public FuzzerSecurityIssueLow(String message) {
+ super(message);
+ }
+
+ public FuzzerSecurityIssueLow(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FuzzerSecurityIssueLow(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java
new file mode 100644
index 00000000..f0de4ce7
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/FuzzerSecurityIssueMedium.java
@@ -0,0 +1,39 @@
+// Copyright 2021 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.api;
+
+/**
+ * Thrown to indicate that a fuzz target has detected a medium severity security issue rather than a
+ * normal bug.
+ *
+ * There is only a semantical but no functional difference between throwing exceptions of this type
+ * or any other. However, automated fuzzing platforms can use the extra information to handle the
+ * detected issues appropriately.
+ */
+public class FuzzerSecurityIssueMedium extends RuntimeException {
+ public FuzzerSecurityIssueMedium() {}
+
+ public FuzzerSecurityIssueMedium(String message) {
+ super(message);
+ }
+
+ public FuzzerSecurityIssueMedium(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FuzzerSecurityIssueMedium(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java b/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java
new file mode 100644
index 00000000..1c564a78
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/HookType.java
@@ -0,0 +1,24 @@
+// Copyright 2021 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.api;
+
+/**
+ * The type of a {@link MethodHook}.
+ */
+public enum HookType {
+ BEFORE,
+ REPLACE,
+ AFTER,
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
new file mode 100644
index 00000000..e45f7600
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/Jazzer.java
@@ -0,0 +1,499 @@
+// Copyright 2021 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.api;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Helper class with static methods that interact with Jazzer at runtime.
+ */
+final public class Jazzer {
+ private static Class<?> jazzerInternal = null;
+
+ private static MethodHandle traceStrcmp = null;
+ private static MethodHandle traceStrstr = null;
+ private static MethodHandle traceMemcmp = null;
+
+ private static MethodHandle consume = null;
+ private static MethodHandle autofuzzFunction1 = null;
+ private static MethodHandle autofuzzFunction2 = null;
+ private static MethodHandle autofuzzFunction3 = null;
+ private static MethodHandle autofuzzFunction4 = null;
+ private static MethodHandle autofuzzFunction5 = null;
+ private static MethodHandle autofuzzConsumer1 = null;
+ private static MethodHandle autofuzzConsumer2 = null;
+ private static MethodHandle autofuzzConsumer3 = null;
+ private static MethodHandle autofuzzConsumer4 = null;
+ private static MethodHandle autofuzzConsumer5 = null;
+
+ static {
+ try {
+ jazzerInternal = Class.forName("com.code_intelligence.jazzer.runtime.JazzerInternal");
+ Class<?> traceDataFlowNativeCallbacks =
+ Class.forName("com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks");
+
+ // Use method handles for hints as the calls are potentially performance critical.
+ MethodType traceStrcmpType =
+ MethodType.methodType(void.class, String.class, String.class, int.class, int.class);
+ traceStrcmp = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceStrcmp", traceStrcmpType);
+ MethodType traceStrstrType =
+ MethodType.methodType(void.class, String.class, String.class, int.class);
+ traceStrstr = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceStrstr", traceStrstrType);
+ MethodType traceMemcmpType =
+ MethodType.methodType(void.class, byte[].class, byte[].class, int.class, int.class);
+ traceMemcmp = MethodHandles.publicLookup().findStatic(
+ traceDataFlowNativeCallbacks, "traceMemcmp", traceMemcmpType);
+
+ Class<?> metaClass = Class.forName("com.code_intelligence.jazzer.autofuzz.Meta");
+ MethodType consumeType =
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Class.class);
+ consume = MethodHandles.publicLookup().findStatic(metaClass, "consume", consumeType);
+
+ autofuzzFunction1 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Function1.class));
+ autofuzzFunction2 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Function2.class));
+ autofuzzFunction3 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Function3.class));
+ autofuzzFunction4 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Function4.class));
+ autofuzzFunction5 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(Object.class, FuzzedDataProvider.class, Function5.class));
+ autofuzzConsumer1 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer1.class));
+ autofuzzConsumer2 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer2.class));
+ autofuzzConsumer3 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer3.class));
+ autofuzzConsumer4 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer4.class));
+ autofuzzConsumer5 = MethodHandles.publicLookup().findStatic(metaClass, "autofuzz",
+ MethodType.methodType(void.class, FuzzedDataProvider.class, Consumer5.class));
+ } catch (ClassNotFoundException ignore) {
+ // Not running in the context of the agent. This is fine as long as no methods are called on
+ // this class.
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ // This should never happen as the Jazzer API is loaded from the agent and thus should always
+ // match the version of the runtime classes.
+ System.err.println("ERROR: Incompatible version of the Jazzer API detected, please update.");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private Jazzer() {}
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Function1} with (partially) specified
+ * type variables, e.g. {@code (Function1<String, ?>) String::new}.
+ * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke
+ * the function.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) {
+ try {
+ return (R) autofuzzFunction1.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ // Not reached.
+ return null;
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Function2} with (partially) specified
+ * type variables.
+ * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke
+ * the function.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) {
+ try {
+ return (R) autofuzzFunction2.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ // Not reached.
+ return null;
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Function3} with (partially) specified
+ * type variables.
+ * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke
+ * the function.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) {
+ try {
+ return (R) autofuzzFunction3.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ // Not reached.
+ return null;
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Function4} with (partially) specified
+ * type variables.
+ * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke
+ * the function.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4, R> R autofuzz(
+ FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) {
+ try {
+ return (R) autofuzzFunction4.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ // Not reached.
+ return null;
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Function5} with (partially) specified
+ * type variables.
+ * @return the return value of {@code func}, or {@code null} if {@code autofuzz} failed to invoke
+ * the function.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4, T5, R> R autofuzz(
+ FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) {
+ try {
+ return (R) autofuzzFunction5.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ // Not reached.
+ return null;
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Consumer1} with explicitly specified
+ * type variable.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) {
+ try {
+ autofuzzConsumer1.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Consumer2} with (partially) specified
+ * type variables.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) {
+ try {
+ autofuzzConsumer2.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Consumer3} with (partially) specified
+ * type variables.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) {
+ try {
+ autofuzzConsumer3.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Consumer4} with (partially) specified
+ * type variables.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ public static <T1, T2, T3, T4> void autofuzz(
+ FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) {
+ try {
+ autofuzzConsumer4.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ }
+
+ /**
+ * Attempts to invoke {@code func} with arguments created automatically from the fuzzer input
+ * using only public methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to execute {@code func} in
+ * meaningful ways for a number of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param func a method reference for the function to autofuzz. If there are multiple overloads,
+ * resolve ambiguities by explicitly casting to {@link Consumer5} with (partially) specified
+ * type variables.
+ * @throws Throwable any {@link Throwable} thrown by {@code func}, or an {@link
+ * AutofuzzConstructionException} if autofuzz failed to construct the arguments for the call.
+ * The {@link Throwable} is thrown unchecked.
+ */
+ public static <T1, T2, T3, T4, T5> void autofuzz(
+ FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) {
+ try {
+ autofuzzConsumer5.invoke(data, func);
+ } catch (AutofuzzInvocationException e) {
+ rethrowUnchecked(e.getCause());
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ }
+ }
+
+ /**
+ * Attempts to construct an instance of {@code type} from the fuzzer input using only public
+ * methods available on the classpath.
+ *
+ * <b>Note:</b> This function is inherently heuristic and may fail to return meaningful values for
+ * a variety of reasons.
+ *
+ * @param data the {@link FuzzedDataProvider} instance provided to {@code fuzzerTestOneInput}.
+ * @param type the {@link Class} to construct an instance of.
+ * @return an instance of {@code type} constructed from the fuzzer input, or {@code null} if
+ * autofuzz failed to create an instance.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T consume(FuzzedDataProvider data, Class<T> type) {
+ try {
+ return (T) consume.invokeExact(data, type);
+ } catch (AutofuzzConstructionException ignored) {
+ return null;
+ } catch (Throwable t) {
+ rethrowUnchecked(t);
+ // Not reached.
+ return null;
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code
+ * target}.
+ *
+ * If the relation between the raw fuzzer input and the value of {@code current} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * achieve equality.
+ *
+ * @param current a non-constant string observed during fuzz target execution
+ * @param target a string that {@code current} should become equal to, but currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsEquality(String current, String target, int id) {
+ try {
+ traceStrcmp.invokeExact(current, target, 1, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code current} equal to {@code
+ * target}.
+ *
+ * If the relation between the raw fuzzer input and the value of {@code current} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * achieve equality.
+ *
+ * @param current a non-constant byte array observed during fuzz target execution
+ * @param target a byte array that {@code current} should become equal to, but currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsEquality(byte[] current, byte[] target, int id) {
+ try {
+ traceMemcmp.invokeExact(current, target, 1, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Instructs the fuzzer to guide its mutations towards making {@code haystack} contain {@code
+ * needle} as a substring.
+ *
+ * If the relation between the raw fuzzer input and the value of {@code haystack} is relatively
+ * complex, running the fuzzer with the argument {@code -use_value_profile=1} may be necessary to
+ * satisfy the substring check.
+ *
+ * @param haystack a non-constant string observed during fuzz target execution
+ * @param needle a string that should be contained in {@code haystack} as a substring, but
+ * currently isn't
+ * @param id a (probabilistically) unique identifier for this particular compare hint
+ */
+ public static void guideTowardsContainment(String haystack, String needle, int id) {
+ try {
+ traceStrstr.invokeExact(haystack, needle, id);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Make Jazzer report the provided {@link Throwable} as a finding.
+ *
+ * <b>Note:</b> This method must only be called from a method hook. In a
+ * fuzz target, simply throw an exception to trigger a finding.
+ * @param finding the finding that Jazzer should report
+ */
+ public static void reportFindingFromHook(Throwable finding) {
+ try {
+ jazzerInternal.getMethod("reportFindingFromHook", Throwable.class).invoke(null, finding);
+ } catch (NullPointerException | IllegalAccessException | NoSuchMethodException e) {
+ // We can only reach this point if the runtime is not in the classpath, but it must be if
+ // hooks work and this function should only be called from them.
+ System.err.println("ERROR: Jazzer.reportFindingFromHook must be called from a method hook");
+ System.exit(1);
+ } catch (InvocationTargetException e) {
+ // reportFindingFromHook throws a HardToCatchThrowable, which will bubble up wrapped in an
+ // InvocationTargetException that should not be stopped here.
+ if (e.getCause().getClass().getName().endsWith(".HardToCatchError")) {
+ throw(Error) e.getCause();
+ } else {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // Rethrows a (possibly checked) exception while avoiding a throws declaration.
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> void rethrowUnchecked(Throwable t) throws T {
+ throw(T) t;
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java
new file mode 100644
index 00000000..0d17a4a0
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHook.java
@@ -0,0 +1,183 @@
+// Copyright 2021 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.api;
+
+import java.lang.annotation.Documented;
+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.lang.invoke.MethodType;
+
+/**
+ * Registers this method as a hook that should run after the method
+ * specified by the annotation parameters has returned.
+ * <p>
+ * This method will be called after every call to the target method and has
+ * access to its return value. The target method is specified by
+ * {@link #targetClassName()} and {@link #targetMethod()}. In case of an
+ * overloaded method, {@link #targetMethodDescriptor()} can be used to restrict
+ * the application of the hook to a particular overload.
+ * <p>
+ * The signature of the annotated method must be as follows (this does not
+ * restrict the method name and parameter names, which are arbitrary),
+ * depending on the value of {@link #type()}:
+ *
+ * <dl>
+ * <dt><span class="strong">{@link HookType#BEFORE}</span>
+ * <dd>
+ * <pre>{@code
+ * public static void hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ * }</pre>
+ * Arguments:
+ * <p><ul>
+ * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the
+ * original method. The original method can be invoked via
+ * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This
+ * requires passing {@code thisObject} as the first argument if the method is
+ * not static. This argument can be {@code null}.
+ * <li>{@code thisObject}: An {@link Object} containing the implicit
+ * {@code this} argument to the original method. If the original method is
+ * static, this argument will be {@code null}.
+ * <li>{@code arguments}: An array of {@link Object}s containing the arguments
+ * passed to the original method. Primitive types (e.g. {@code boolean}) will be
+ * wrapped into their corresponding wrapper type (e.g. {@link Boolean}).
+ * <li>{@code hookId}: A random {@code int} identifying the particular call
+ * site.This can be used to derive additional coverage information.
+ * </ul>
+ *
+ * <dt><span class="strong">{@link HookType#REPLACE}</span>
+ * <dd>
+ * <pre>{@code
+ * public static Object hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ * }</pre>
+ * The return type may alternatively be taken to be the exact return type of
+ * target method or a wrapper type thereof. The returned object will be casted
+ * and unwrapped automatically.
+ * <p>
+ * Arguments:
+ * <p><ul>
+ * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the
+ * original method. The original method can be invoked via
+ * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This
+ * requires passing {@code thisObject} as the first argument if the method is
+ * not static. This argument can be {@code null}.
+ * <li>{@code thisObject}: An {@link Object} containing the implicit
+ * {@code this} argument to the original method. If the original method is
+ * static, this argument will be {@code null}.
+ * <li>{@code arguments}: An array of {@link Object}s containing the arguments
+ * passed to the original method. Primitive types (e.g. {@code boolean}) will be
+ * wrapped into their corresponding wrapper type (e.g. {@link Boolean}).
+ * <li>{@code hookId}: A random {@code int} identifying the particular call
+ * site.This can be used to derive additional coverage information.
+ * </ul><p>
+ * <p>
+ * Return value: the value that should take the role of the value the target
+ * method would have returned
+ *
+ * <dt><span class="strong">{@link HookType#AFTER}</span>
+ * <dd>
+ * <pre>{@code
+ * public static void hook(MethodHandle method, Object thisObject, Object[] arguments, int hookId,
+ * Object returnValue)
+ * }</pre>
+ * Arguments:
+ * <p><ul>
+ * <li>{@code method}: A {@link java.lang.invoke.MethodHandle} representing the
+ * original method. The original method can be invoked via
+ * {@link java.lang.invoke.MethodHandle#invokeWithArguments(Object...)}. This
+ * requires passing {@code thisObject} as the first argument if the method is
+ * not static. This argument can be {@code null}.
+ * <li>{@code thisObject}: An {@link Object} containing the implicit
+ * {@code this} argument to the original method. If the original method is
+ * static, this argument will be {@code null}.
+ * <li>{@code arguments}: An array of {@link Object}s containing the arguments
+ * passed to the original method. Primitive types (e.g. {@code boolean}) will be
+ * wrapped into their corresponding wrapper type (e.g. {@link Boolean}).
+ * <li>{@code hookId}: A random {@code int} identifying the particular call
+ * site.This can be used to derive additional coverage information.
+ * <li>{@code returnValue}: An {@link Object} containing the return value of the
+ * invocation of the original method. Primitive types (e.g. {@code boolean})
+ * will be wrapped into their corresponding wrapper type (e.g. {@link Boolean}).
+ * If the original method has return type {@code void}, this value will be
+ * {@code null}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Repeatable(MethodHooks.class)
+@Documented
+public @interface MethodHook {
+ /**
+ * The time at which the annotated method should be called.
+ * <p>
+ * If this is {@link HookType#BEFORE}, the annotated method will be called
+ * before the target method and has access to its arguments.
+ * <p>
+ * If this is {@link HookType#REPLACE}, the annotated method will be called
+ * instead of the target method. It has access to its arguments and can
+ * return a value that will replace the target method's return value.
+ * <p>
+ * If this is {@link HookType#AFTER}, the annotated method will be called
+ * after the target method and has access to its arguments and return
+ * value.
+ *
+ * @return when the hook should be called
+ */
+ HookType type();
+
+ /**
+ * The name of the class that contains the method that should be hooked,
+ * as returned by {@link Class#getName()}.
+ * <p>
+ * Examples:
+ * <p><ul>
+ * <li>{@link String}: {@code "java.lang.String"}
+ * <li>{@link java.nio.file.FileSystem}: {@code "java.nio.file.FileSystem"}
+ * </ul><p>
+ *
+ * @return the name of the class containing the method to be hooked
+ */
+ String targetClassName();
+
+ /**
+ * The name of the method to be hooked. Use {@code "<init>"} for
+ * constructors.
+ * <p>
+ * Examples:
+ * <p><ul>
+ * <li>{@link String#equals(Object)}: {@code "equals"}
+ * <li>{@link String#String()}: {@code "<init>"}
+ * </ul><p>
+ *
+ * @return the name of the method to be hooked
+ */
+ String targetMethod();
+
+ /**
+ * The descriptor of the method to be hooked. This is only needed if there
+ * are multiple methods with the same name and not all of them should be
+ * hooked.
+ * <p>
+ * The descriptor of a method is an internal representation of the method's
+ * signature, which includes the types of its parameters and its return
+ * value. For more information on descriptors, see the
+ * <a href=https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-4.html#jvms-4.3.3>JVM
+ * Specification, Section 4.3.3</a> and {@link MethodType#toMethodDescriptorString()}
+ *
+ * @return the descriptor of the method to be hooked
+ */
+ String targetMethodDescriptor() default "";
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java
new file mode 100644
index 00000000..7eec24b3
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/api/MethodHooks.java
@@ -0,0 +1,31 @@
+// Copyright 2021 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.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Internal helper allowing to apply multiple {@link MethodHook} annotations to the same method.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface MethodHooks {
+ MethodHook[] value();
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java
new file mode 100644
index 00000000..2fbed971
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzCodegenVisitor.java
@@ -0,0 +1,117 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import java.util.Stack;
+import java.util.stream.Collectors;
+
+public class AutofuzzCodegenVisitor {
+ private final Stack<Group> groups = new Stack<>();
+ private int variableCounter = 0;
+
+ AutofuzzCodegenVisitor() {
+ init();
+ }
+
+ private void init() {
+ pushGroup("", "", "");
+ }
+
+ public void pushGroup(String prefix, String delimiter, String suffix) {
+ groups.push(new Group(prefix, delimiter, suffix));
+ }
+
+ public void pushElement(String element) {
+ groups.peek().push(element);
+ }
+
+ public void popElement() {
+ groups.peek().pop();
+ }
+
+ public void popGroup() {
+ if (groups.size() == 1) {
+ throw new AutofuzzError(
+ "popGroup must be called exactly once for every pushGroup: " + toDebugString());
+ }
+ pushElement(groups.pop().toString());
+ }
+
+ public String generate() {
+ if (groups.size() != 1) {
+ throw new AutofuzzError(
+ "popGroup must be called exactly once for every pushGroup: " + toDebugString());
+ }
+ return groups.pop().toString();
+ }
+
+ public void addCharLiteral(char c) {
+ pushElement("'" + escapeForLiteral(Character.toString(c)) + "'");
+ }
+
+ public void addStringLiteral(String string) {
+ pushElement('"' + escapeForLiteral(string) + '"');
+ }
+
+ public String uniqueVariableName() {
+ return String.format("autofuzzVariable%s", variableCounter++);
+ }
+
+ private String escapeForLiteral(String string) {
+ // The list of escape sequences is taken from:
+ // https://docs.oracle.com/javase/tutorial/java/data/characters.html
+ return string.replace("\t", "\\t")
+ .replace("\b", "\\b")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\f", "\\f")
+ .replace("\f", "\\f")
+ .replace("\"", "\\\"")
+ .replace("'", "\\'")
+ .replace("\\", "\\\\");
+ }
+
+ private String toDebugString() {
+ return groups.stream()
+ .map(group -> group.elements.stream().collect(Collectors.joining(", ", "[", "]")))
+ .collect(Collectors.joining(", ", "[", "]"));
+ }
+
+ private static class Group {
+ private final String prefix;
+ private final String delimiter;
+ private final String suffix;
+ private final Stack<String> elements = new Stack<>();
+
+ Group(String prefix, String delimiter, String suffix) {
+ this.prefix = prefix;
+ this.delimiter = delimiter;
+ this.suffix = suffix;
+ }
+
+ public void push(String element) {
+ elements.push(element);
+ }
+
+ public void pop() {
+ elements.pop();
+ }
+
+ @Override
+ public String toString() {
+ return elements.stream().collect(Collectors.joining(delimiter, prefix, suffix));
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java
new file mode 100644
index 00000000..a94b385d
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/AutofuzzError.java
@@ -0,0 +1,31 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+/**
+ * An error indicating an internal error in the autofuzz functionality.
+ */
+public class AutofuzzError extends Error {
+ private static final String MESSAGE_TRAILER = String.format(
+ "%nPlease file an issue at:%n https://github.com/CodeIntelligenceTesting/jazzer/issues/new/choose");
+
+ public AutofuzzError(String message) {
+ super(message + MESSAGE_TRAILER);
+ }
+
+ public AutofuzzError(String message, Throwable cause) {
+ super(message + MESSAGE_TRAILER, cause);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
new file mode 100644
index 00000000..779f79cb
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
@@ -0,0 +1,17 @@
+java_library(
+ name = "autofuzz",
+ srcs = [
+ "AutofuzzCodegenVisitor.java",
+ "AutofuzzError.java",
+ "FuzzTarget.java",
+ "Meta.java",
+ "YourAverageJavaClass.java",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/utils",
+ "@com_github_classgraph_classgraph//:classgraph",
+ "@com_github_jhalterman_typetools//:typetools",
+ ],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java
new file mode 100644
index 00000000..8c344621
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/FuzzTarget.java
@@ -0,0 +1,267 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import com.code_intelligence.jazzer.api.AutofuzzConstructionException;
+import com.code_intelligence.jazzer.api.AutofuzzInvocationException;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.code_intelligence.jazzer.utils.SimpleGlobMatcher;
+import com.code_intelligence.jazzer.utils.Utils;
+import java.io.Closeable;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class FuzzTarget {
+ private static final long MAX_EXECUTIONS_WITHOUT_INVOCATION = 100;
+
+ private static String methodReference;
+ private static Executable[] targetExecutables;
+ private static Map<Executable, Class<?>[]> throwsDeclarations;
+ private static Set<SimpleGlobMatcher> ignoredExceptionMatchers;
+ private static long executionsSinceLastInvocation = 0;
+ private static AutofuzzCodegenVisitor codegenVisitor;
+
+ public static void fuzzerInitialize(String[] args) {
+ if (args.length == 0 || !args[0].contains("::")) {
+ System.err.println(
+ "Expected the argument to --autofuzz to be a method reference (e.g. System.out::println)");
+ System.exit(1);
+ }
+ methodReference = args[0];
+ String[] parts = methodReference.split("::", 2);
+ String className = parts[0];
+ String methodNameAndOptionalDescriptor = parts[1];
+ String methodName;
+ String descriptor;
+ int descriptorStart = methodNameAndOptionalDescriptor.indexOf('(');
+ if (descriptorStart != -1) {
+ methodName = methodNameAndOptionalDescriptor.substring(0, descriptorStart);
+ // URL decode the descriptor to allow copy-pasting from javadoc links such as:
+ // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#valueOf(char%5B%5D)
+ try {
+ descriptor =
+ URLDecoder.decode(methodNameAndOptionalDescriptor.substring(descriptorStart), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // UTF-8 is always supported.
+ e.printStackTrace();
+ System.exit(1);
+ return;
+ }
+ } else {
+ methodName = methodNameAndOptionalDescriptor;
+ descriptor = null;
+ }
+
+ Class<?> targetClass;
+ try {
+ // Explicitly invoking static initializers to trigger some coverage in the code.
+ targetClass = Class.forName(className, true, ClassLoader.getSystemClassLoader());
+ } catch (ClassNotFoundException e) {
+ System.err.printf(
+ "Failed to find class %s for autofuzz, please ensure it is contained in the classpath "
+ + "specified with --cp and specify the full package name%n",
+ className);
+ e.printStackTrace();
+ System.exit(1);
+ return;
+ }
+
+ boolean isConstructor = methodName.equals("new");
+ if (isConstructor) {
+ targetExecutables =
+ Arrays.stream(targetClass.getConstructors())
+ .filter(constructor
+ -> descriptor == null
+ || Utils.getReadableDescriptor(constructor).equals(descriptor))
+ .toArray(Executable[] ::new);
+ } else {
+ targetExecutables =
+ Arrays.stream(targetClass.getMethods())
+ .filter(method
+ -> method.getName().equals(methodName)
+ && (descriptor == null
+ || Utils.getReadableDescriptor(method).equals(descriptor)))
+ .toArray(Executable[] ::new);
+ }
+ if (targetExecutables.length == 0) {
+ if (isConstructor) {
+ if (descriptor == null) {
+ System.err.printf(
+ "Failed to find accessible constructors in class %s for autofuzz.%n", className);
+ } else {
+ System.err.printf(
+ "Failed to find accessible constructors with signature %s in class %s for autofuzz.%n"
+ + "Accessible constructors:%n%s",
+ descriptor, className,
+ Arrays.stream(targetClass.getConstructors())
+ .map(method
+ -> String.format("%s::new%s", method.getDeclaringClass().getName(),
+ Utils.getReadableDescriptor(method)))
+ .distinct()
+ .collect(Collectors.joining(System.lineSeparator())));
+ }
+ } else {
+ if (descriptor == null) {
+ System.err.printf("Failed to find accessible methods named %s in class %s for autofuzz.%n"
+ + "Accessible methods:%n%s",
+ methodName, className,
+ Arrays.stream(targetClass.getMethods())
+ .map(method
+ -> String.format(
+ "%s::%s", method.getDeclaringClass().getName(), method.getName()))
+ .distinct()
+ .collect(Collectors.joining(System.lineSeparator())));
+ } else {
+ System.err.printf(
+ "Failed to find accessible methods named %s with signature %s in class %s for autofuzz.%n"
+ + "Accessible methods with that name:%n%s",
+ methodName, descriptor, className,
+ Arrays.stream(targetClass.getMethods())
+ .filter(method -> method.getName().equals(methodName))
+ .map(method
+ -> String.format("%s::%s%s", method.getDeclaringClass().getName(),
+ method.getName(), Utils.getReadableDescriptor(method)))
+ .distinct()
+ .collect(Collectors.joining(System.lineSeparator())));
+ }
+ }
+ System.exit(1);
+ }
+
+ ignoredExceptionMatchers = Arrays.stream(args)
+ .skip(1)
+ .filter(s -> s.contains("*"))
+ .map(SimpleGlobMatcher::new)
+ .collect(Collectors.toSet());
+
+ List<Class<?>> alwaysIgnore =
+ Arrays.stream(args)
+ .skip(1)
+ .filter(s -> !s.contains("*"))
+ .map(name -> {
+ try {
+ return ClassLoader.getSystemClassLoader().loadClass(name);
+ } catch (ClassNotFoundException e) {
+ System.err.printf("Failed to find class '%s' specified in --autofuzz_ignore", name);
+ System.exit(1);
+ }
+ throw new Error("Not reached");
+ })
+ .collect(Collectors.toList());
+ throwsDeclarations =
+ Arrays.stream(targetExecutables)
+ .collect(Collectors.toMap(method
+ -> method,
+ method
+ -> Stream.concat(Arrays.stream(method.getExceptionTypes()), alwaysIgnore.stream())
+ .toArray(Class[] ::new)));
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) throws Throwable {
+ if (Meta.isDebug()) {
+ codegenVisitor = new AutofuzzCodegenVisitor();
+ }
+ Executable targetExecutable;
+ if (FuzzTarget.targetExecutables.length == 1) {
+ targetExecutable = FuzzTarget.targetExecutables[0];
+ } else {
+ targetExecutable = data.pickValue(FuzzTarget.targetExecutables);
+ }
+ Object returnValue = null;
+ try {
+ if (targetExecutable instanceof Method) {
+ returnValue = Meta.autofuzz(data, (Method) targetExecutable, codegenVisitor);
+ } else {
+ returnValue = Meta.autofuzz(data, (Constructor<?>) targetExecutable, codegenVisitor);
+ }
+ executionsSinceLastInvocation = 0;
+ if (codegenVisitor != null) {
+ System.err.println(codegenVisitor.generate());
+ }
+ } catch (AutofuzzConstructionException e) {
+ if (Meta.isDebug()) {
+ e.printStackTrace();
+ }
+ // Ignore exceptions thrown while constructing the parameters for the target method. We can
+ // only guess how to generate valid parameters and any exceptions thrown while doing so
+ // are most likely on us. However, if this happens too often, Autofuzz got stuck and we should
+ // let the user know.
+ executionsSinceLastInvocation++;
+ if (executionsSinceLastInvocation >= MAX_EXECUTIONS_WITHOUT_INVOCATION) {
+ System.err.printf("Failed to generate valid arguments to '%s' in %d attempts; giving up%n",
+ methodReference, executionsSinceLastInvocation);
+ System.exit(1);
+ } else if (executionsSinceLastInvocation == MAX_EXECUTIONS_WITHOUT_INVOCATION / 2) {
+ // The application under test might perform classpath modifications or create classes
+ // dynamically that implement interfaces or extend abstract classes. Rescanning the
+ // classpath might help with constructing objects.
+ Meta.rescanClasspath();
+ }
+ } catch (AutofuzzInvocationException e) {
+ executionsSinceLastInvocation = 0;
+ Throwable cause = e.getCause();
+ Class<?> causeClass = cause.getClass();
+ // Do not report exceptions declared to be thrown by the method under test.
+ for (Class<?> declaredThrow : throwsDeclarations.get(targetExecutable)) {
+ if (declaredThrow.isAssignableFrom(causeClass)) {
+ return;
+ }
+ }
+
+ if (ignoredExceptionMatchers.stream().anyMatch(m -> m.matches(causeClass.getName()))) {
+ return;
+ }
+ cleanStackTraces(cause);
+ throw cause;
+ } catch (Throwable t) {
+ System.err.println("Unexpected exception encountered during autofuzz");
+ t.printStackTrace();
+ System.exit(1);
+ } finally {
+ if (returnValue instanceof Closeable) {
+ ((Closeable) returnValue).close();
+ }
+ }
+ }
+
+ // Removes all stack trace elements that live in the Java standard library, internal JDK classes
+ // or the autofuzz package from the bottom of all stack frames.
+ private static void cleanStackTraces(Throwable t) {
+ Throwable cause = t;
+ while (cause != null) {
+ StackTraceElement[] elements = cause.getStackTrace();
+ int firstInterestingPos;
+ for (firstInterestingPos = elements.length - 1; firstInterestingPos > 0;
+ firstInterestingPos--) {
+ String className = elements[firstInterestingPos].getClassName();
+ if (!className.startsWith("com.code_intelligence.jazzer.autofuzz")
+ && !className.startsWith("java.") && !className.startsWith("jdk.")) {
+ break;
+ }
+ }
+ cause.setStackTrace(Arrays.copyOfRange(elements, 0, firstInterestingPos + 1));
+ cause = cause.getCause();
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java
new file mode 100644
index 00000000..96980530
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/Meta.java
@@ -0,0 +1,619 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import com.code_intelligence.jazzer.api.AutofuzzConstructionException;
+import com.code_intelligence.jazzer.api.AutofuzzInvocationException;
+import com.code_intelligence.jazzer.api.Consumer1;
+import com.code_intelligence.jazzer.api.Consumer2;
+import com.code_intelligence.jazzer.api.Consumer3;
+import com.code_intelligence.jazzer.api.Consumer4;
+import com.code_intelligence.jazzer.api.Consumer5;
+import com.code_intelligence.jazzer.api.Function1;
+import com.code_intelligence.jazzer.api.Function2;
+import com.code_intelligence.jazzer.api.Function3;
+import com.code_intelligence.jazzer.api.Function4;
+import com.code_intelligence.jazzer.api.Function5;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.code_intelligence.jazzer.utils.Utils;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfoList;
+import io.github.classgraph.ScanResult;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import net.jodah.typetools.TypeResolver;
+import net.jodah.typetools.TypeResolver.Unknown;
+
+public class Meta {
+ static WeakHashMap<Class<?>, List<Class<?>>> implementingClassesCache = new WeakHashMap<>();
+ static WeakHashMap<Class<?>, List<Class<?>>> nestedBuilderClassesCache = new WeakHashMap<>();
+ static WeakHashMap<Class<?>, List<Method>> originalObjectCreationMethodsCache =
+ new WeakHashMap<>();
+ static WeakHashMap<Class<?>, List<Method>> cascadingBuilderMethodsCache = new WeakHashMap<>();
+
+ public static Object autofuzz(FuzzedDataProvider data, Method method) {
+ return autofuzz(data, method, null);
+ }
+
+ static Object autofuzz(FuzzedDataProvider data, Method method, AutofuzzCodegenVisitor visitor) {
+ Object result;
+ if (Modifier.isStatic(method.getModifiers())) {
+ if (visitor != null) {
+ // This group will always have two elements: The class name and the method call.
+ visitor.pushGroup(
+ String.format("%s.", method.getDeclaringClass().getCanonicalName()), "", "");
+ }
+ result = autofuzz(data, method, null, visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ } else {
+ if (visitor != null) {
+ // This group will always have two elements: The thisObject and the method call.
+ visitor.pushGroup("", ".", "");
+ }
+ Object thisObject = consume(data, method.getDeclaringClass(), visitor);
+ if (thisObject == null) {
+ throw new AutofuzzConstructionException();
+ }
+ result = autofuzz(data, method, thisObject, visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ }
+ return result;
+ }
+
+ public static Object autofuzz(FuzzedDataProvider data, Method method, Object thisObject) {
+ return autofuzz(data, method, thisObject, null);
+ }
+
+ static Object autofuzz(
+ FuzzedDataProvider data, Method method, Object thisObject, AutofuzzCodegenVisitor visitor) {
+ if (visitor != null) {
+ visitor.pushGroup(String.format("%s(", method.getName()), ", ", ")");
+ }
+ Object[] arguments = consumeArguments(data, method, visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ try {
+ return method.invoke(thisObject, arguments);
+ } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
+ // We should ensure that the arguments fed into the method are always valid.
+ throw new AutofuzzError(getDebugSummary(method, thisObject, arguments), e);
+ } catch (InvocationTargetException e) {
+ throw new AutofuzzInvocationException(e.getCause());
+ }
+ }
+
+ public static <R> R autofuzz(FuzzedDataProvider data, Constructor<R> constructor) {
+ return autofuzz(data, constructor, null);
+ }
+
+ static <R> R autofuzz(
+ FuzzedDataProvider data, Constructor<R> constructor, AutofuzzCodegenVisitor visitor) {
+ if (visitor != null) {
+ // getCanonicalName is correct also for nested classes.
+ visitor.pushGroup(
+ String.format("new %s(", constructor.getDeclaringClass().getCanonicalName()), ", ", ")");
+ }
+ Object[] arguments = consumeArguments(data, constructor, visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ try {
+ return constructor.newInstance(arguments);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
+ // This should never be reached as the logic in consume should prevent us from e.g. calling
+ // constructors of abstract classes or private constructors.
+ throw new AutofuzzError(getDebugSummary(constructor, null, arguments), e);
+ } catch (InvocationTargetException e) {
+ throw new AutofuzzInvocationException(e.getCause());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1> void autofuzz(FuzzedDataProvider data, Consumer1<T1> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer1.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2> void autofuzz(FuzzedDataProvider data, Consumer2<T1, T2> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer2.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3> void autofuzz(FuzzedDataProvider data, Consumer3<T1, T2, T3> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer3.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4> void autofuzz(
+ FuzzedDataProvider data, Consumer4<T1, T2, T3, T4> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer4.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4, T5> void autofuzz(
+ FuzzedDataProvider data, Consumer5<T1, T2, T3, T4, T5> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Consumer5.class, func.getClass());
+ func.accept((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3),
+ (T5) consumeChecked(data, types, 4));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, R> R autofuzz(FuzzedDataProvider data, Function1<T1, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function1.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, R> R autofuzz(FuzzedDataProvider data, Function2<T1, T2, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function2.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, R> R autofuzz(FuzzedDataProvider data, Function3<T1, T2, T3, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function3.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4, R> R autofuzz(
+ FuzzedDataProvider data, Function4<T1, T2, T3, T4, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function4.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T1, T2, T3, T4, T5, R> R autofuzz(
+ FuzzedDataProvider data, Function5<T1, T2, T3, T4, T5, R> func) {
+ Class<?>[] types = TypeResolver.resolveRawArguments(Function5.class, func.getClass());
+ return func.apply((T1) consumeChecked(data, types, 0), (T2) consumeChecked(data, types, 1),
+ (T3) consumeChecked(data, types, 2), (T4) consumeChecked(data, types, 3),
+ (T5) consumeChecked(data, types, 4));
+ }
+
+ public static Object consume(FuzzedDataProvider data, Class<?> type) {
+ return consume(data, type, null);
+ }
+
+ static Object consume(FuzzedDataProvider data, Class<?> type, AutofuzzCodegenVisitor visitor) {
+ if (type == byte.class || type == Byte.class) {
+ byte result = data.consumeByte();
+ if (visitor != null)
+ visitor.pushElement(String.format("(byte) %s", result));
+ return result;
+ } else if (type == short.class || type == Short.class) {
+ short result = data.consumeShort();
+ if (visitor != null)
+ visitor.pushElement(String.format("(short) %s", result));
+ return result;
+ } else if (type == int.class || type == Integer.class) {
+ int result = data.consumeInt();
+ if (visitor != null)
+ visitor.pushElement(Integer.toString(result));
+ return result;
+ } else if (type == long.class || type == Long.class) {
+ long result = data.consumeLong();
+ if (visitor != null)
+ visitor.pushElement(String.format("%sL", result));
+ return result;
+ } else if (type == float.class || type == Float.class) {
+ float result = data.consumeFloat();
+ if (visitor != null)
+ visitor.pushElement(String.format("%sF", result));
+ return result;
+ } else if (type == double.class || type == Double.class) {
+ double result = data.consumeDouble();
+ if (visitor != null)
+ visitor.pushElement(Double.toString(result));
+ return result;
+ } else if (type == boolean.class || type == Boolean.class) {
+ boolean result = data.consumeBoolean();
+ if (visitor != null)
+ visitor.pushElement(Boolean.toString(result));
+ return result;
+ } else if (type == char.class || type == Character.class) {
+ char result = data.consumeChar();
+ if (visitor != null)
+ visitor.addCharLiteral(result);
+ return result;
+ }
+ // Return null for non-primitive and non-boxed types in ~5% of the cases.
+ // TODO: We might want to return null for boxed types sometimes, but this is complicated by the
+ // fact that TypeUtils can't distinguish between a primitive type and its wrapper and may
+ // thus easily cause false-positive NullPointerExceptions.
+ if (!type.isPrimitive() && data.consumeByte((byte) 0, (byte) 19) == 0) {
+ if (visitor != null)
+ visitor.pushElement("null");
+ return null;
+ }
+ if (type == String.class || type == CharSequence.class) {
+ String result = data.consumeString(consumeArrayLength(data, 1));
+ if (visitor != null)
+ visitor.addStringLiteral(result);
+ return result;
+ } else if (type.isArray()) {
+ if (type == byte[].class) {
+ byte[] result = data.consumeBytes(consumeArrayLength(data, Byte.BYTES));
+ if (visitor != null) {
+ visitor.pushElement(IntStream.range(0, result.length)
+ .mapToObj(i -> "(byte) " + result[i])
+ .collect(Collectors.joining(", ", "new byte[]{", "}")));
+ }
+ return result;
+ } else if (type == int[].class) {
+ int[] result = data.consumeInts(consumeArrayLength(data, Integer.BYTES));
+ if (visitor != null) {
+ visitor.pushElement(Arrays.stream(result)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(", ", "new int[]{", "}")));
+ }
+ return result;
+ } else if (type == short[].class) {
+ short[] result = data.consumeShorts(consumeArrayLength(data, Short.BYTES));
+ if (visitor != null) {
+ visitor.pushElement(IntStream.range(0, result.length)
+ .mapToObj(i -> "(short) " + result[i])
+ .collect(Collectors.joining(", ", "new short[]{", "}")));
+ }
+ return result;
+ } else if (type == long[].class) {
+ long[] result = data.consumeLongs(consumeArrayLength(data, Long.BYTES));
+ if (visitor != null) {
+ visitor.pushElement(Arrays.stream(result)
+ .mapToObj(e -> e + "L")
+ .collect(Collectors.joining(", ", "new long[]{", "}")));
+ }
+ return result;
+ } else if (type == boolean[].class) {
+ boolean[] result = data.consumeBooleans(consumeArrayLength(data, 1));
+ if (visitor != null) {
+ visitor.pushElement(
+ Arrays.toString(result).replace(']', '}').replace("[", "new boolean[]{"));
+ }
+ return result;
+ } else {
+ if (visitor != null) {
+ visitor.pushGroup(
+ String.format("new %s[]{", type.getComponentType().getName()), ", ", "}");
+ }
+ int remainingBytesBeforeFirstElementCreation = data.remainingBytes();
+ Object firstElement = consume(data, type.getComponentType(), visitor);
+ int remainingBytesAfterFirstElementCreation = data.remainingBytes();
+ int sizeOfElementEstimate =
+ remainingBytesBeforeFirstElementCreation - remainingBytesAfterFirstElementCreation;
+ Object array = Array.newInstance(
+ type.getComponentType(), consumeArrayLength(data, sizeOfElementEstimate));
+ for (int i = 0; i < Array.getLength(array); i++) {
+ if (i == 0) {
+ Array.set(array, i, firstElement);
+ } else {
+ Array.set(array, i, consume(data, type.getComponentType(), visitor));
+ }
+ }
+ if (visitor != null) {
+ if (Array.getLength(array) == 0) {
+ // We implicitly pushed the first element with the call to consume above, but it is not
+ // part of the array.
+ visitor.popElement();
+ }
+ visitor.popGroup();
+ }
+ return array;
+ }
+ } else if (type == ByteArrayInputStream.class || type == InputStream.class) {
+ byte[] array = data.consumeBytes(consumeArrayLength(data, Byte.BYTES));
+ if (visitor != null) {
+ visitor.pushElement(IntStream.range(0, array.length)
+ .mapToObj(i -> "(byte) " + array[i])
+ .collect(Collectors.joining(
+ ", ", "new java.io.ByteArrayInputStream(new byte[]{", "})")));
+ }
+ return new ByteArrayInputStream(array);
+ } else if (type.isEnum()) {
+ Enum<?> enumValue = (Enum<?>) data.pickValue(type.getEnumConstants());
+ if (visitor != null) {
+ visitor.pushElement(String.format("%s.%s", type.getName(), enumValue.name()));
+ }
+ return enumValue;
+ } else if (type == Class.class) {
+ if (visitor != null)
+ visitor.pushElement(String.format("%s.class", YourAverageJavaClass.class.getName()));
+ return YourAverageJavaClass.class;
+ } else if (type == Method.class) {
+ if (visitor != null) {
+ throw new AutofuzzError("codegen has not been implemented for Method.class");
+ }
+ return data.pickValue(sortExecutables(YourAverageJavaClass.class.getMethods()));
+ } else if (type == Constructor.class) {
+ if (visitor != null) {
+ throw new AutofuzzError("codegen has not been implemented for Constructor.class");
+ }
+ return data.pickValue(sortExecutables(YourAverageJavaClass.class.getConstructors()));
+ } else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) {
+ List<Class<?>> implementingClasses = implementingClassesCache.get(type);
+ if (implementingClasses == null) {
+ ClassGraph classGraph =
+ new ClassGraph().enableClassInfo().enableInterClassDependencies().rejectPackages(
+ "jaz.*");
+ if (!isTest()) {
+ classGraph.rejectPackages("com.code_intelligence.jazzer.*");
+ }
+ try (ScanResult result = classGraph.scan()) {
+ ClassInfoList children =
+ type.isInterface() ? result.getClassesImplementing(type) : result.getSubclasses(type);
+ implementingClasses =
+ children.getStandardClasses().filter(cls -> !cls.isAbstract()).loadClasses();
+ implementingClassesCache.put(type, implementingClasses);
+ }
+ }
+ if (implementingClasses.isEmpty()) {
+ if (isDebug()) {
+ throw new AutofuzzConstructionException(String.format(
+ "Could not find classes implementing %s on the classpath", type.getName()));
+ } else {
+ throw new AutofuzzConstructionException();
+ }
+ }
+ if (visitor != null) {
+ // This group will always have a single element: The instance of the implementing class.
+ visitor.pushGroup(String.format("(%s) ", type.getName()), "", "");
+ }
+ Object result = consume(data, data.pickValue(implementingClasses), visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ return result;
+ } else if (type.getConstructors().length > 0) {
+ Constructor<?> constructor = data.pickValue(sortExecutables(type.getConstructors()));
+ boolean applySetters = constructor.getParameterCount() == 0;
+ if (visitor != null && applySetters) {
+ // Embed the instance creation and setters into an immediately invoked lambda expression to
+ // turn them into an expression.
+ String uniqueVariableName = visitor.uniqueVariableName();
+ visitor.pushGroup(String.format("((java.util.function.Supplier<%1$s>) (() -> {%1$s %2$s = ",
+ type.getCanonicalName(), uniqueVariableName),
+ String.format("; %s.", uniqueVariableName),
+ String.format("; return %s;})).get()", uniqueVariableName));
+ }
+ Object obj = autofuzz(data, constructor, visitor);
+ if (applySetters) {
+ List<Method> potentialSetters = getPotentialSetters(type);
+ if (!potentialSetters.isEmpty()) {
+ List<Method> pickedSetters =
+ data.pickValues(potentialSetters, data.consumeInt(0, potentialSetters.size()));
+ for (Method setter : pickedSetters) {
+ autofuzz(data, setter, obj, visitor);
+ }
+ }
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ }
+ return obj;
+ }
+ // We are out of more or less canonical ways to construct an instance of this class and have to
+ // resort to more heuristic approaches.
+
+ // First, try to find nested classes with names ending in Builder and call a subset of their
+ // chaining methods.
+ List<Class<?>> nestedBuilderClasses = getNestedBuilderClasses(type);
+ if (!nestedBuilderClasses.isEmpty()) {
+ Class<?> pickedBuilder = data.pickValue(nestedBuilderClasses);
+ List<Method> cascadingBuilderMethods = getCascadingBuilderMethods(pickedBuilder);
+ List<Method> originalObjectCreationMethods = getOriginalObjectCreationMethods(pickedBuilder);
+
+ int pickedMethodsNumber = data.consumeInt(0, cascadingBuilderMethods.size());
+ List<Method> pickedMethods = data.pickValues(cascadingBuilderMethods, pickedMethodsNumber);
+ Method builderMethod = data.pickValue(originalObjectCreationMethods);
+
+ if (visitor != null) {
+ // Group for the chain of builder methods.
+ visitor.pushGroup("", ".", "");
+ }
+ Object builderObj =
+ autofuzz(data, data.pickValue(sortExecutables(pickedBuilder.getConstructors())), visitor);
+ for (Method method : pickedMethods) {
+ builderObj = autofuzz(data, method, builderObj, visitor);
+ }
+
+ try {
+ Object obj = autofuzz(data, builderMethod, builderObj, visitor);
+ if (visitor != null) {
+ visitor.popGroup();
+ }
+ return obj;
+ } catch (Exception e) {
+ throw new AutofuzzConstructionException(e);
+ }
+ }
+
+ // We ran out of ways to construct an instance of the requested type. If in debug mode, report
+ // more detailed information.
+ if (!isDebug()) {
+ throw new AutofuzzConstructionException();
+ } else {
+ String summary = String.format(
+ "Failed to generate instance of %s:%nAccessible constructors: %s%nNested subclasses: %s%n",
+ type.getName(),
+ Arrays.stream(type.getConstructors())
+ .map(Utils::getReadableDescriptor)
+ .collect(Collectors.joining(", ")),
+ Arrays.stream(type.getClasses()).map(Class::getName).collect(Collectors.joining(", ")));
+ throw new AutofuzzConstructionException(summary);
+ }
+ }
+
+ static void rescanClasspath() {
+ implementingClassesCache.clear();
+ }
+
+ static boolean isTest() {
+ String value = System.getenv("JAZZER_AUTOFUZZ_TESTING");
+ return value != null && !value.isEmpty();
+ }
+
+ static boolean isDebug() {
+ String value = System.getenv("JAZZER_AUTOFUZZ_DEBUG");
+ return value != null && !value.isEmpty();
+ }
+
+ private static int consumeArrayLength(FuzzedDataProvider data, int sizeOfElement) {
+ // Spend at most half of the fuzzer input bytes so that the remaining arguments that require
+ // construction still have non-trivial data to work with.
+ int bytesToSpend = data.remainingBytes() / 2;
+ return bytesToSpend / Math.max(sizeOfElement, 1);
+ }
+
+ private static String getDebugSummary(
+ Executable executable, Object thisObject, Object[] arguments) {
+ return String.format("%nMethod: %s::%s%s%nthis: %s%nArguments: %s",
+ executable.getDeclaringClass().getName(), executable.getName(),
+ Utils.getReadableDescriptor(executable), thisObject,
+ Arrays.stream(arguments)
+ .map(arg -> arg == null ? "null" : arg.toString())
+ .collect(Collectors.joining(", ")));
+ }
+
+ private static <T extends Executable> List<T> sortExecutables(T[] executables) {
+ List<T> list = Arrays.asList(executables);
+ sortExecutables(list);
+ return list;
+ }
+
+ private static void sortExecutables(List<? extends Executable> executables) {
+ executables.sort(Comparator.comparing(Executable::getName).thenComparing(Utils::getDescriptor));
+ }
+
+ private static void sortClasses(List<? extends Class<?>> classes) {
+ classes.sort(Comparator.comparing(Class::getName));
+ }
+
+ private static List<Class<?>> getNestedBuilderClasses(Class<?> type) {
+ List<Class<?>> nestedBuilderClasses = nestedBuilderClassesCache.get(type);
+ if (nestedBuilderClasses == null) {
+ nestedBuilderClasses = Arrays.stream(type.getClasses())
+ .filter(cls -> cls.getName().endsWith("Builder"))
+ .filter(cls -> !getOriginalObjectCreationMethods(cls).isEmpty())
+ .collect(Collectors.toList());
+ sortClasses(nestedBuilderClasses);
+ nestedBuilderClassesCache.put(type, nestedBuilderClasses);
+ }
+ return nestedBuilderClasses;
+ }
+
+ private static List<Method> getOriginalObjectCreationMethods(Class<?> builder) {
+ List<Method> originalObjectCreationMethods = originalObjectCreationMethodsCache.get(builder);
+ if (originalObjectCreationMethods == null) {
+ originalObjectCreationMethods =
+ Arrays.stream(builder.getMethods())
+ .filter(m -> m.getReturnType() == builder.getEnclosingClass())
+ .collect(Collectors.toList());
+ sortExecutables(originalObjectCreationMethods);
+ originalObjectCreationMethodsCache.put(builder, originalObjectCreationMethods);
+ }
+ return originalObjectCreationMethods;
+ }
+
+ private static List<Method> getCascadingBuilderMethods(Class<?> builder) {
+ List<Method> cascadingBuilderMethods = cascadingBuilderMethodsCache.get(builder);
+ if (cascadingBuilderMethods == null) {
+ cascadingBuilderMethods = Arrays.stream(builder.getMethods())
+ .filter(m -> m.getReturnType() == builder)
+ .collect(Collectors.toList());
+ sortExecutables(cascadingBuilderMethods);
+ cascadingBuilderMethodsCache.put(builder, cascadingBuilderMethods);
+ }
+ return cascadingBuilderMethods;
+ }
+
+ private static List<Method> getPotentialSetters(Class<?> type) {
+ List<Method> potentialSetters = new ArrayList<>();
+ Method[] methods = type.getMethods();
+ for (Method method : methods) {
+ if (void.class.equals(method.getReturnType()) && method.getParameterCount() == 1
+ && method.getName().startsWith("set")) {
+ potentialSetters.add(method);
+ }
+ }
+ sortExecutables(potentialSetters);
+ return potentialSetters;
+ }
+
+ private static Object[] consumeArguments(
+ FuzzedDataProvider data, Executable executable, AutofuzzCodegenVisitor visitor) {
+ Object[] result;
+ try {
+ result = Arrays.stream(executable.getParameterTypes())
+ .map((type) -> consume(data, type, visitor))
+ .toArray();
+ return result;
+ } catch (AutofuzzConstructionException e) {
+ // Do not nest AutofuzzConstructionExceptions.
+ throw e;
+ } catch (AutofuzzInvocationException e) {
+ // If an invocation fails while creating the arguments for another invocation, the exception
+ // should not be reported, so we rewrap it.
+ throw new AutofuzzConstructionException(e.getCause());
+ } catch (Throwable t) {
+ throw new AutofuzzConstructionException(t);
+ }
+ }
+
+ private static Object consumeChecked(FuzzedDataProvider data, Class<?>[] types, int i) {
+ if (types[i] == Unknown.class) {
+ throw new AutofuzzError("Failed to determine type of argument " + (i + 1));
+ }
+ Object result;
+ try {
+ result = consume(data, types[i]);
+ } catch (AutofuzzConstructionException e) {
+ // Do not nest AutofuzzConstructionExceptions.
+ throw e;
+ } catch (AutofuzzInvocationException e) {
+ // If an invocation fails while creating the arguments for another invocation, the exception
+ // should not be reported, so we rewrap it.
+ throw new AutofuzzConstructionException(e.getCause());
+ } catch (Throwable t) {
+ throw new AutofuzzConstructionException(t);
+ }
+ if (result != null && !types[i].isAssignableFrom(result.getClass())) {
+ throw new AutofuzzError("consume returned " + result.getClass() + ", but need " + types[i]);
+ }
+ return result;
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java
new file mode 100644
index 00000000..452ca878
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/autofuzz/YourAverageJavaClass.java
@@ -0,0 +1,229 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+// Returned by Meta when asked to construct a Class object. Its purpose is to be a relatively
+// "interesting" Java data class that can serve as the target of methods that perform some kind of
+// reflection or deserialization.
+public class YourAverageJavaClass implements Cloneable, Closeable, Serializable {
+ public byte aByte;
+ public boolean aBoolean;
+ public double aDouble;
+ public float aFloat;
+ public int anInt;
+ public transient int transientInt;
+ public long aLong;
+ public short aShort;
+ public volatile short volatileShort;
+ public String string;
+ public byte[] bytes;
+ public List<YourAverageJavaClass> list;
+ public Map<String, YourAverageJavaClass> map;
+
+ // Everything below has been automatically generated (apart from a minor modification to clone());
+
+ public YourAverageJavaClass(byte aByte, boolean aBoolean, double aDouble, float aFloat, int anInt,
+ int transientInt, long aLong, short aShort, short volatileShort, String string) {
+ this.aByte = aByte;
+ this.aBoolean = aBoolean;
+ this.aDouble = aDouble;
+ this.aFloat = aFloat;
+ this.anInt = anInt;
+ this.transientInt = transientInt;
+ this.aLong = aLong;
+ this.aShort = aShort;
+ this.volatileShort = volatileShort;
+ this.string = string;
+ }
+
+ public YourAverageJavaClass() {}
+
+ public YourAverageJavaClass(byte aByte, boolean aBoolean, double aDouble, float aFloat, int anInt,
+ int transientInt, long aLong, short aShort, short volatileShort, String string, byte[] bytes,
+ List<YourAverageJavaClass> list, Map<String, YourAverageJavaClass> map) {
+ this.aByte = aByte;
+ this.aBoolean = aBoolean;
+ this.aDouble = aDouble;
+ this.aFloat = aFloat;
+ this.anInt = anInt;
+ this.transientInt = transientInt;
+ this.aLong = aLong;
+ this.aShort = aShort;
+ this.volatileShort = volatileShort;
+ this.string = string;
+ this.bytes = bytes;
+ this.list = list;
+ this.map = map;
+ }
+
+ public byte getaByte() {
+ return aByte;
+ }
+
+ public void setaByte(byte aByte) {
+ this.aByte = aByte;
+ }
+
+ public boolean isaBoolean() {
+ return aBoolean;
+ }
+
+ public void setaBoolean(boolean aBoolean) {
+ this.aBoolean = aBoolean;
+ }
+
+ public double getaDouble() {
+ return aDouble;
+ }
+
+ public void setaDouble(double aDouble) {
+ this.aDouble = aDouble;
+ }
+
+ public float getaFloat() {
+ return aFloat;
+ }
+
+ public void setaFloat(float aFloat) {
+ this.aFloat = aFloat;
+ }
+
+ public int getAnInt() {
+ return anInt;
+ }
+
+ public void setAnInt(int anInt) {
+ this.anInt = anInt;
+ }
+
+ public int getTransientInt() {
+ return transientInt;
+ }
+
+ public void setTransientInt(int transientInt) {
+ this.transientInt = transientInt;
+ }
+
+ public long getaLong() {
+ return aLong;
+ }
+
+ public void setaLong(long aLong) {
+ this.aLong = aLong;
+ }
+
+ public short getaShort() {
+ return aShort;
+ }
+
+ public void setaShort(short aShort) {
+ this.aShort = aShort;
+ }
+
+ public short getVolatileShort() {
+ return volatileShort;
+ }
+
+ public void setVolatileShort(short volatileShort) {
+ this.volatileShort = volatileShort;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public void setString(String string) {
+ this.string = string;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public void setBytes(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ public List<YourAverageJavaClass> getList() {
+ return list;
+ }
+
+ public void setList(List<YourAverageJavaClass> list) {
+ this.list = list;
+ }
+
+ public Map<String, YourAverageJavaClass> getMap() {
+ return map;
+ }
+
+ public void setMap(Map<String, YourAverageJavaClass> map) {
+ this.map = map;
+ }
+
+ @Override
+ public YourAverageJavaClass clone() {
+ try {
+ YourAverageJavaClass clone = (YourAverageJavaClass) super.clone();
+ clone.transientInt = transientInt + 1;
+ clone.volatileShort = (short) (volatileShort - 1);
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (!(o instanceof YourAverageJavaClass))
+ return false;
+ YourAverageJavaClass that = (YourAverageJavaClass) o;
+ return aByte == that.aByte && aBoolean == that.aBoolean
+ && Double.compare(that.aDouble, aDouble) == 0 && Float.compare(that.aFloat, aFloat) == 0
+ && anInt == that.anInt && transientInt == that.transientInt && aLong == that.aLong
+ && aShort == that.aShort && volatileShort == that.volatileShort
+ && Objects.equals(string, that.string) && Arrays.equals(bytes, that.bytes)
+ && Objects.equals(list, that.list) && Objects.equals(map, that.map);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(aByte, aBoolean, aDouble, aFloat, anInt, transientInt, aLong, aShort,
+ volatileShort, string, list, map);
+ result = 31 * result + Arrays.hashCode(bytes);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "YourAverageJavaClass{"
+ + "aByte=" + aByte + ", aBoolean=" + aBoolean + ", aDouble=" + aDouble + ", aFloat="
+ + aFloat + ", anInt=" + anInt + ", transientInt=" + transientInt + ", aLong=" + aLong
+ + ", aShort=" + aShort + ", volatileShort=" + volatileShort + ", string='" + string + '\''
+ + ", bytes=" + Arrays.toString(bytes) + ", list=" + list + ", map=" + map + '}';
+ }
+
+ @Override
+ public void close() throws IOException {}
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel
new file mode 100644
index 00000000..fceda64c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/generated/BUILD.bazel
@@ -0,0 +1,40 @@
+java_binary(
+ name = "NoThrowDoclet",
+ srcs = ["NoThrowDoclet.java"],
+ create_executable = False,
+ tags = ["manual"],
+)
+
+# To regenerate the list of methods, ensure that your local JDK is as recent as possible and contains `lib/src.zip`.
+# This will be the case if you are using the release binaries of the OpenJDK or if the `openjdk-<version>-source`
+# package is installed.
+# Then, execute
+# agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh
+# from the Bazel root and copy the file into
+# org.jacoco.core/src/org/jacoco/core/internal/flow/java_no_throw_methods_list.dat
+# in the CodeIntelligenceTesting/jacoco repository.
+genrule(
+ name = "java_no_throw_methods_list",
+ srcs = [
+ "@local_jdk//:lib/src.zip",
+ ],
+ outs = [
+ "java_no_throw_methods_list.dat.generated",
+ ],
+ cmd = """
+ TMP=$$(mktemp -d) && \
+ unzip $(execpath @local_jdk//:lib/src.zip) -d $$TMP && \
+ $(execpath @local_jdk//:bin/javadoc) \
+ -doclet com.code_intelligence.jazzer.generated.NoThrowDoclet \
+ -docletpath $(execpath :NoThrowDoclet_deploy.jar) \
+ --module java.base \
+ --source-path $$TMP/java.base \
+ --out $@ && \
+ sort -o $@ $@ && \
+ rm -rf $$TMP""",
+ tags = ["manual"],
+ tools = [
+ ":NoThrowDoclet_deploy.jar",
+ "@local_jdk//:bin/javadoc",
+ ],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java b/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java
new file mode 100644
index 00000000..1b52a228
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/generated/NoThrowDoclet.java
@@ -0,0 +1,215 @@
+// Copyright 2021 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.generated;
+
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.ThrowsTree;
+import com.sun.source.util.DocTrees;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.ModuleElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import jdk.javadoc.doclet.Doclet;
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
+
+/**
+ * A Doclet that extracts a list of all method signatures in {@code java.*} that are declared not to
+ * throw any exceptions, including {@link RuntimeException} but excluding {@link
+ * VirtualMachineError}.
+ *
+ * Crucially, whereas the throws declaration of a method does not contain subclasses of {@link
+ * RuntimeException}, the {@code @throws} Javadoc tag does.
+ */
+public class NoThrowDoclet implements Doclet {
+ private BufferedWriter out;
+
+ @Override
+ public void init(Locale locale, Reporter reporter) {}
+
+ @Override
+ public String getName() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public Set<? extends Option> getSupportedOptions() {
+ return Set.of(new Option() {
+ @Override
+ public int getArgumentCount() {
+ return 1;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Output file (.kt)";
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.STANDARD;
+ }
+
+ @Override
+ public List<String> getNames() {
+ return List.of("--out");
+ }
+
+ @Override
+ public String getParameters() {
+ return "<output file (.kt)>";
+ }
+
+ @Override
+ public boolean process(String option, List<String> args) {
+ try {
+ out = new BufferedWriter(new FileWriter(args.get(0)));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return null;
+ }
+
+ private String toDescriptor(TypeMirror type) {
+ switch (type.getKind()) {
+ case BOOLEAN:
+ return "Z";
+ case BYTE:
+ return "B";
+ case CHAR:
+ return "C";
+ case DOUBLE:
+ return "D";
+ case FLOAT:
+ return "F";
+ case INT:
+ return "I";
+ case LONG:
+ return "J";
+ case SHORT:
+ return "S";
+ case VOID:
+ return "V";
+ case ARRAY:
+ return "[" + toDescriptor(((ArrayType) type).getComponentType());
+ case DECLARED:
+ return "L" + getFullyQualifiedName((DeclaredType) type) + ";";
+ case TYPEVAR:
+ return "Ljava/lang/Object;";
+ }
+ throw new IllegalArgumentException(
+ "Unexpected kind " + type.getKind() + ": " + type.toString());
+ }
+
+ private String getFullyQualifiedName(DeclaredType declaredType) {
+ TypeElement element = (TypeElement) declaredType.asElement();
+ return element.getQualifiedName().toString().replace('.', '/');
+ }
+
+ private void handleExecutableElement(DocTrees trees, ExecutableElement executable)
+ throws IOException {
+ if (!executable.getModifiers().contains(Modifier.PUBLIC))
+ return;
+
+ DocCommentTree tree = trees.getDocCommentTree(executable);
+ if (tree != null) {
+ for (DocTree tag : tree.getBlockTags()) {
+ if (tag instanceof ThrowsTree) {
+ return;
+ }
+ }
+ }
+
+ String methodName = executable.getSimpleName().toString();
+ String className =
+ ((TypeElement) executable.getEnclosingElement()).getQualifiedName().toString();
+ String internalClassName = className.replace('.', '/');
+
+ String paramTypeDescriptors = executable.getParameters()
+ .stream()
+ .map(VariableElement::asType)
+ .map(this::toDescriptor)
+ .collect(Collectors.joining(""));
+ String returnTypeDescriptor = toDescriptor(executable.getReturnType());
+ String methodDescriptor = String.format("(%s)%s", paramTypeDescriptors, returnTypeDescriptor);
+
+ out.write(String.format("%s#%s#%s%n", internalClassName, methodName, methodDescriptor));
+ }
+
+ public void handleTypeElement(DocTrees trees, TypeElement typeElement) throws IOException {
+ List<ExecutableElement> executables =
+ ElementFilter.constructorsIn(typeElement.getEnclosedElements());
+ executables.addAll(ElementFilter.methodsIn(typeElement.getEnclosedElements()));
+ for (ExecutableElement executableElement : executables) {
+ handleExecutableElement(trees, executableElement);
+ }
+ }
+
+ @Override
+ public boolean run(DocletEnvironment docletEnvironment) {
+ try {
+ DocTrees trees = docletEnvironment.getDocTrees();
+ for (ModuleElement moduleElement :
+ ElementFilter.modulesIn(docletEnvironment.getSpecifiedElements())) {
+ for (PackageElement packageElement :
+ ElementFilter.packagesIn(moduleElement.getEnclosedElements())) {
+ if (packageElement.getQualifiedName().toString().startsWith("java.")) {
+ for (TypeElement typeElement :
+ ElementFilter.typesIn(packageElement.getEnclosedElements())) {
+ ElementKind kind = typeElement.getKind();
+ if (kind == ElementKind.CLASS || kind == ElementKind.ENUM
+ || kind == ElementKind.INTERFACE) {
+ handleTypeElement(trees, typeElement);
+ }
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh b/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh
new file mode 100755
index 00000000..1463c602
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/generated/update_java_no_throw_methods_list.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env sh
+# Copyright 2021 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.
+
+set -e
+bazel build //agent/src/main/java/com/code_intelligence/jazzer/generated:java_no_throw_methods_list
+cp bazel-bin/agent/src/main/java/com/code_intelligence/jazzer/generated/java_no_throw_methods_list.dat.generated agent/src/main/java/com/code_intelligence/jazzer/generated/java_no_throw_methods_list.dat
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
new file mode 100644
index 00000000..50d10705
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,45 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar")
+
+kt_jvm_library(
+ name = "instrumentor",
+ srcs = [
+ "ClassInstrumentor.kt",
+ "CoverageRecorder.kt",
+ "DescriptorUtils.kt",
+ "DeterministicRandom.kt",
+ "EdgeCoverageInstrumentor.kt",
+ "Hook.kt",
+ "HookInstrumentor.kt",
+ "HookMethodVisitor.kt",
+ "Instrumentor.kt",
+ "TraceDataFlowInstrumentor.kt",
+ ],
+ visibility = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/agent:__pkg__",
+ "//agent/src/test/java/com/code_intelligence/jazzer/instrumentor:__pkg__",
+ ],
+ deps = [
+ ":shaded_deps",
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime",
+ "//agent/src/main/java/com/code_intelligence/jazzer/utils",
+ "@com_github_classgraph_classgraph//:classgraph",
+ "@com_github_jetbrains_kotlin//:kotlin-reflect",
+ ],
+)
+
+jar_jar(
+ name = "shaded_deps",
+ input_jar = "unshaded_deps_deploy.jar",
+ rules = "shade_rules",
+)
+
+java_binary(
+ name = "unshaded_deps",
+ create_executable = False,
+ runtime_deps = [
+ "@jazzer_jacoco//:jacoco_internal",
+ "@jazzer_ow2_asm//:asm",
+ "@jazzer_ow2_asm//:asm_commons",
+ ],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt
new file mode 100644
index 00000000..f6728a1a
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/ClassInstrumentor.kt
@@ -0,0 +1,53 @@
+// Copyright 2021 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.instrumentor
+
+fun extractClassFileMajorVersion(classfileBuffer: ByteArray): Int {
+ return ((classfileBuffer[6].toInt() and 0xff) shl 8) or (classfileBuffer[7].toInt() and 0xff)
+}
+
+class ClassInstrumentor constructor(bytecode: ByteArray) {
+
+ var instrumentedBytecode = bytecode
+ private set
+
+ fun coverage(initialEdgeId: Int): Int {
+ val edgeCoverageInstrumentor = EdgeCoverageInstrumentor(initialEdgeId)
+ instrumentedBytecode = edgeCoverageInstrumentor.instrument(instrumentedBytecode)
+ return edgeCoverageInstrumentor.numEdges
+ }
+
+ fun traceDataFlow(instrumentations: Set<InstrumentationType>) {
+ instrumentedBytecode = TraceDataFlowInstrumentor(instrumentations).instrument(instrumentedBytecode)
+ }
+
+ fun hooks(hooks: Iterable<Hook>) {
+ instrumentedBytecode = HookInstrumentor(
+ hooks,
+ java6Mode = extractClassFileMajorVersion(instrumentedBytecode) < 51
+ ).instrument(instrumentedBytecode)
+ }
+
+ companion object {
+ init {
+ try {
+ // Calls JNI_OnLoad_jazzer_initialize in the driver, which registers the native methods.
+ System.loadLibrary("jazzer_initialize")
+ } catch (_: UnsatisfiedLinkError) {
+ // Make it possible to use (parts of) the agent without the driver.
+ }
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt
new file mode 100644
index 00000000..65956189
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/CoverageRecorder.kt
@@ -0,0 +1,229 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.runtime.CoverageMap
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.CoverageBuilder
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionData
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataReader
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataWriter
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfo
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.SessionInfoStore
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.data.CRC64
+import com.code_intelligence.jazzer.utils.ClassNameGlobber
+import io.github.classgraph.ClassGraph
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.time.Instant
+import java.util.UUID
+
+private data class InstrumentedClassInfo(
+ val classId: Long,
+ val initialEdgeId: Int,
+ val nextEdgeId: Int,
+ val bytecode: ByteArray,
+)
+
+object CoverageRecorder {
+ var classNameGlobber = ClassNameGlobber(emptyList(), emptyList())
+ private val instrumentedClassInfo = mutableMapOf<String, InstrumentedClassInfo>()
+ private var startTimestamp: Instant? = null
+ private val additionalCoverage = mutableSetOf<Int>()
+
+ fun recordInstrumentedClass(internalClassName: String, bytecode: ByteArray, firstId: Int, numIds: Int) {
+ if (startTimestamp == null)
+ startTimestamp = Instant.now()
+ instrumentedClassInfo[internalClassName] = InstrumentedClassInfo(
+ CRC64.classId(bytecode), firstId, firstId + numIds, bytecode
+ )
+ }
+
+ /**
+ * Manually records coverage IDs based on the current state of [CoverageMap.mem].
+ * Should be called after static initializers have run.
+ */
+ @JvmStatic
+ fun updateCoveredIdsWithCoverageMap() {
+ val mem = CoverageMap.mem
+ val size = mem.capacity()
+ additionalCoverage.addAll((0 until size).filter { mem[it] > 0 })
+ }
+
+ @JvmStatic
+ fun replayCoveredIds() {
+ val mem = CoverageMap.mem
+ for (coverageId in additionalCoverage) {
+ mem.put(coverageId, 1)
+ }
+ }
+
+ @JvmStatic
+ fun computeFileCoverage(coveredIds: IntArray): String {
+ val coverage = analyzeCoverage(coveredIds.toSet()) ?: return "No classes were instrumented"
+ return coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Branch coverage:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ val counter = fileCoverage.branchCounter
+ val percentage = 100 * counter.coveredRatio
+ "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)"
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Line coverage:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ val counter = fileCoverage.lineCounter
+ val percentage = 100 * counter.coveredRatio
+ "${fileCoverage.name}: ${counter.coveredCount}/${counter.totalCount} (${percentage.format(2)}%)"
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Incompletely covered lines:\n",
+ postfix = "\n\n"
+ ) { fileCoverage ->
+ "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter {
+ val instructions = fileCoverage.getLine(it).instructionCounter
+ instructions.coveredCount in 1 until instructions.totalCount
+ }.toString()
+ } + coverage.sourceFiles.joinToString(
+ "\n",
+ prefix = "Missed lines:\n",
+ ) { fileCoverage ->
+ "${fileCoverage.name}: " + (fileCoverage.firstLine..fileCoverage.lastLine).filter {
+ val instructions = fileCoverage.getLine(it).instructionCounter
+ instructions.coveredCount == 0 && instructions.totalCount > 0
+ }.toString()
+ }
+ }
+
+ private fun Double.format(digits: Int) = "%.${digits}f".format(this)
+
+ fun dumpJacocoCoverage(coveredIds: Set<Int>): ByteArray? {
+ // Update the list of covered IDs with the coverage information for the current run.
+ updateCoveredIdsWithCoverageMap()
+
+ val dumpTimestamp = Instant.now()
+ val outStream = ByteArrayOutputStream()
+ val outWriter = ExecutionDataWriter(outStream)
+ // Return null if no class has been instrumented.
+ val startTimestamp = startTimestamp ?: return null
+ outWriter.visitSessionInfo(
+ SessionInfo(UUID.randomUUID().toString(), startTimestamp.epochSecond, dumpTimestamp.epochSecond)
+ )
+
+ val sortedCoveredIds = (additionalCoverage + coveredIds).sorted().toIntArray()
+ for ((internalClassName, info) in instrumentedClassInfo) {
+ // Determine the subarray of coverage IDs in sortedCoveredIds that contains the IDs generated while
+ // instrumenting the current class. Since the ID array is sorted, use binary search.
+ var coveredIdsStart = sortedCoveredIds.binarySearch(info.initialEdgeId)
+ if (coveredIdsStart < 0) {
+ coveredIdsStart = -(coveredIdsStart + 1)
+ }
+ var coveredIdsEnd = sortedCoveredIds.binarySearch(info.nextEdgeId)
+ if (coveredIdsEnd < 0) {
+ coveredIdsEnd = -(coveredIdsEnd + 1)
+ }
+ if (coveredIdsStart == coveredIdsEnd) {
+ // No coverage data for the class.
+ continue
+ }
+ check(coveredIdsStart in 0 until coveredIdsEnd && coveredIdsEnd <= sortedCoveredIds.size) {
+ "Invalid range [$coveredIdsStart, $coveredIdsEnd) with coveredIds.size=${sortedCoveredIds.size}"
+ }
+ // Generate a probes array for the current class only, i.e., mapping info.initialEdgeId to 0.
+ val probes = BooleanArray(info.nextEdgeId - info.initialEdgeId)
+ (coveredIdsStart until coveredIdsEnd).asSequence()
+ .map {
+ val globalEdgeId = sortedCoveredIds[it]
+ globalEdgeId - info.initialEdgeId
+ }
+ .forEach { classLocalEdgeId ->
+ probes[classLocalEdgeId] = true
+ }
+ outWriter.visitClassExecution(ExecutionData(info.classId, internalClassName, probes))
+ }
+ return outStream.toByteArray()
+ }
+
+ fun analyzeCoverage(coveredIds: Set<Int>): CoverageBuilder? {
+ return try {
+ val coverage = CoverageBuilder()
+ analyzeAllUncoveredClasses(coverage)
+ val rawExecutionData = dumpJacocoCoverage(coveredIds) ?: return null
+ val executionDataStore = ExecutionDataStore()
+ val sessionInfoStore = SessionInfoStore()
+ ByteArrayInputStream(rawExecutionData).use { stream ->
+ ExecutionDataReader(stream).run {
+ setExecutionDataVisitor(executionDataStore)
+ setSessionInfoVisitor(sessionInfoStore)
+ read()
+ }
+ }
+ for ((internalClassName, info) in instrumentedClassInfo) {
+ EdgeCoverageInstrumentor(0).analyze(
+ executionDataStore,
+ coverage,
+ info.bytecode,
+ internalClassName
+ )
+ }
+ coverage
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
+ }
+
+ /**
+ * Traverses the entire classpath and analyzes all uncovered classes that match the include/exclude pattern.
+ * The returned [CoverageBuilder] will report coverage information for *all* classes on the classpath, not just
+ * those that were loaded while the fuzzer ran.
+ */
+ private fun analyzeAllUncoveredClasses(coverage: CoverageBuilder): CoverageBuilder {
+ val coveredClassNames = instrumentedClassInfo
+ .keys
+ .asSequence()
+ .map { it.replace('/', '.') }
+ .toSet()
+ val emptyExecutionDataStore = ExecutionDataStore()
+ ClassGraph()
+ .enableClassInfo()
+ .ignoreClassVisibility()
+ .rejectPackages(
+ // Always exclude Jazzer-internal packages (including ClassGraph itself) from coverage reports. Classes
+ // from the Java standard library are never traversed.
+ "com.code_intelligence.jazzer.*",
+ "jaz",
+ )
+ .scan().use { result ->
+ result.allClasses
+ .asSequence()
+ .filter { classInfo -> classNameGlobber.includes(classInfo.name) }
+ .filterNot { classInfo -> classInfo.name in coveredClassNames }
+ .forEach { classInfo ->
+ classInfo.resource.use { resource ->
+ EdgeCoverageInstrumentor(0).analyze(
+ emptyExecutionDataStore,
+ coverage,
+ resource.load(),
+ classInfo.name.replace('.', '/')
+ )
+ }
+ }
+ }
+ return coverage
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt
new file mode 100644
index 00000000..80cbe80b
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtils.kt
@@ -0,0 +1,90 @@
+// Copyright 2021 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.instrumentor
+
+internal fun isPrimitiveType(typeDescriptor: String): Boolean {
+ return typeDescriptor in arrayOf("B", "C", "D", "F", "I", "J", "S", "V", "Z")
+}
+
+private fun isPrimitiveType(typeDescriptor: Char) = isPrimitiveType(typeDescriptor.toString())
+
+internal fun getWrapperTypeDescriptor(typeDescriptor: String): String = when (typeDescriptor) {
+ "B" -> "Ljava/lang/Byte;"
+ "C" -> "Ljava/lang/Character;"
+ "D" -> "Ljava/lang/Double;"
+ "F" -> "Ljava/lang/Float;"
+ "I" -> "Ljava/lang/Integer;"
+ "J" -> "Ljava/lang/Long;"
+ "S" -> "Ljava/lang/Short;"
+ "V" -> "Ljava/lang/Void;"
+ "Z" -> "Ljava/lang/Boolean;"
+ else -> typeDescriptor
+}
+
+// Removes the 'L' and ';' prefix/suffix from signatures to get the full class name.
+// Note that array signatures '[Ljava/lang/String;' already have the correct form.
+internal fun extractInternalClassName(typeDescriptor: String): String {
+ return if (typeDescriptor.startsWith("L") && typeDescriptor.endsWith(";")) {
+ typeDescriptor.substring(1, typeDescriptor.length - 1)
+ } else {
+ typeDescriptor
+ }
+}
+
+internal fun extractParameterTypeDescriptors(methodDescriptor: String): List<String> {
+ require(methodDescriptor.startsWith('(')) { "Method descriptor must start with '('" }
+ val endOfParameterPart = methodDescriptor.indexOf(')') - 1
+ require(endOfParameterPart >= 0) { "Method descriptor must contain ')'" }
+ var remainingDescriptorList = methodDescriptor.substring(1..endOfParameterPart)
+ val parameterDescriptors = mutableListOf<String>()
+ while (remainingDescriptorList.isNotEmpty()) {
+ val nextDescriptor = extractNextTypeDescriptor(remainingDescriptorList)
+ parameterDescriptors.add(nextDescriptor)
+ remainingDescriptorList = remainingDescriptorList.removePrefix(nextDescriptor)
+ }
+ return parameterDescriptors
+}
+
+internal fun extractReturnTypeDescriptor(methodDescriptor: String): String {
+ require(methodDescriptor.startsWith('(')) { "Method descriptor must start with '('" }
+ val endBracketPos = methodDescriptor.indexOf(')')
+ require(endBracketPos >= 0) { "Method descriptor must contain ')'" }
+ val startOfReturnValue = endBracketPos + 1
+ return extractNextTypeDescriptor(methodDescriptor.substring(startOfReturnValue))
+}
+
+private fun extractNextTypeDescriptor(input: String): String {
+ require(input.isNotEmpty()) { "Type descriptor must not be empty" }
+ // Skip over arbitrarily many '[' to support multi-dimensional arrays.
+ val firstNonArrayPrefixCharPos = input.indexOfFirst { it != '[' }
+ require(firstNonArrayPrefixCharPos >= 0) { "Array descriptor must contain type" }
+ val firstTypeChar = input[firstNonArrayPrefixCharPos]
+ return when {
+ // Primitive type
+ isPrimitiveType(firstTypeChar) -> {
+ input.substring(0..firstNonArrayPrefixCharPos)
+ }
+ // Object type
+ firstTypeChar == 'L' -> {
+ val endOfClassNamePos = input.indexOf(';')
+ require(endOfClassNamePos > 0) { "Class type indicated by L must end with ;" }
+ input.substring(0..endOfClassNamePos)
+ }
+ // Invalid type
+ else -> {
+ throw IllegalArgumentException("Invalid type: $firstTypeChar")
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt
new file mode 100644
index 00000000..d4210dc4
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/DeterministicRandom.kt
@@ -0,0 +1,35 @@
+// Copyright 2021 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.instrumentor
+
+import java.security.MessageDigest
+import java.security.SecureRandom
+
+// This RNG is resistant to collisions (even under XOR) but fully deterministic.
+internal class DeterministicRandom(vararg contexts: String) {
+ private val random = SecureRandom.getInstance("SHA1PRNG").apply {
+ val contextHash = MessageDigest.getInstance("SHA-256").run {
+ for (context in contexts) {
+ update(context.toByteArray())
+ }
+ digest()
+ }
+ setSeed(contextHash)
+ }
+
+ fun nextInt(bound: Int) = random.nextInt(bound)
+
+ fun nextInt() = random.nextInt()
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt
new file mode 100644
index 00000000..ba5b7ee9
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/EdgeCoverageInstrumentor.kt
@@ -0,0 +1,201 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.runtime.CoverageMap
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.Analyzer
+import com.code_intelligence.jazzer.third_party.jacoco.core.analysis.ICoverageVisitor
+import com.code_intelligence.jazzer.third_party.jacoco.core.data.ExecutionDataStore
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesAdapter
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.ClassProbesVisitor
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.IClassProbesAdapterFactory
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.flow.JavaNoThrowMethods
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ClassInstrumenter
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeArrayStrategy
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.IProbeInserterFactory
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.InstrSupport
+import com.code_intelligence.jazzer.third_party.jacoco.core.internal.instr.ProbeInserter
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassVisitor
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter
+import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
+import kotlin.math.max
+
+class EdgeCoverageInstrumentor(
+ private val initialEdgeId: Int,
+ private val coverageMapClass: Class<*> = CoverageMap::class.java
+) : Instrumentor {
+ private var nextEdgeId = initialEdgeId
+ private val coverageMapInternalClassName = coverageMapClass.name.replace('.', '/')
+ init {
+ if (isTesting) {
+ JavaNoThrowMethods.isTesting = true
+ }
+ }
+
+ override fun instrument(bytecode: ByteArray): ByteArray {
+ val reader = InstrSupport.classReaderFor(bytecode)
+ val writer = ClassWriter(reader, 0)
+ val version = InstrSupport.getMajorVersion(reader)
+ val visitor = EdgeCoverageClassProbesAdapter(
+ ClassInstrumenter(edgeCoverageProbeArrayStrategy, edgeCoverageProbeInserterFactory, writer),
+ InstrSupport.needsFrames(version)
+ )
+ reader.accept(visitor, ClassReader.EXPAND_FRAMES)
+ return writer.toByteArray()
+ }
+
+ fun analyze(executionData: ExecutionDataStore, coverageVisitor: ICoverageVisitor, bytecode: ByteArray, internalClassName: String) {
+ Analyzer(executionData, coverageVisitor, edgeCoverageClassProbesAdapterFactory).run {
+ analyzeClass(bytecode, internalClassName)
+ }
+ }
+
+ val numEdges
+ get() = nextEdgeId - initialEdgeId
+
+ private val isTesting
+ get() = coverageMapClass != CoverageMap::class.java
+
+ private fun nextEdgeId(): Int {
+ if (nextEdgeId >= CoverageMap.mem.capacity()) {
+ if (!isTesting) {
+ CoverageMap.enlargeCoverageMap()
+ }
+ }
+ return nextEdgeId++
+ }
+
+ /**
+ * The maximal number of stack elements used by [loadCoverageMap].
+ */
+ private val loadCoverageMapStackSize = 1
+
+ /**
+ * Inject bytecode that loads the coverage map into local variable [variable].
+ */
+ private fun loadCoverageMap(mv: MethodVisitor, variable: Int) {
+ mv.apply {
+ visitFieldInsn(
+ Opcodes.GETSTATIC,
+ coverageMapInternalClassName,
+ "mem",
+ "Ljava/nio/ByteBuffer;"
+ )
+ // Stack: mem (maxStack: 1)
+ visitVarInsn(Opcodes.ASTORE, variable)
+ }
+ }
+
+ /**
+ * The maximal number of stack elements used by [instrumentControlFlowEdge].
+ */
+ private val instrumentControlFlowEdgeStackSize = 5
+
+ /**
+ * Inject bytecode instrumentation on a control flow edge with ID [edgeId]. The coverage map can be loaded from
+ * local variable [variable].
+ */
+ private fun instrumentControlFlowEdge(mv: MethodVisitor, edgeId: Int, variable: Int) {
+ mv.apply {
+ visitVarInsn(Opcodes.ALOAD, variable)
+ // Stack: mem
+ push(edgeId)
+ // Stack: mem | edgeId
+ visitInsn(Opcodes.DUP2)
+ // Stack: mem | edgeId | mem | edgeId
+ visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "get", "(I)B", false)
+ // Increment the counter, but ensure that it never stays at 0 after an overflow by incrementing it again in
+ // that case.
+ // This approach performs better than saturating the counter at 255 (see Section 3.3 of
+ // https://www.usenix.org/system/files/woot20-paper-fioraldi.pdf)
+ // Stack: mem | edgeId | counter (sign-extended to int)
+ push(0xff)
+ // Stack: mem | edgeId | counter (sign-extended to int) | 0x000000ff
+ visitInsn(Opcodes.IAND)
+ // Stack: mem | edgeId | counter (zero-extended to int)
+ push(1)
+ // Stack: mem | edgeId | counter | 1
+ visitInsn(Opcodes.IADD)
+ // Stack: mem | edgeId | counter + 1
+ visitInsn(Opcodes.DUP)
+ // Stack: mem | edgeId | counter + 1 | counter + 1
+ push(8)
+ // Stack: mem | edgeId | counter + 1 | counter + 1 | 8 (maxStack: +5)
+ visitInsn(Opcodes.ISHR)
+ // Stack: mem | edgeId | counter + 1 | 1 if the increment overflowed to 0, 0 otherwise
+ visitInsn(Opcodes.IADD)
+ // Stack: mem | edgeId | counter + 2 if the increment overflowed, counter + 1 otherwise
+ visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/nio/ByteBuffer", "put", "(IB)Ljava/nio/ByteBuffer;", false)
+ // Stack: mem
+ visitInsn(Opcodes.POP)
+ if (isTesting) {
+ visitMethodInsn(Opcodes.INVOKESTATIC, coverageMapInternalClassName, "updated", "()V", false)
+ }
+ }
+ }
+
+// The remainder of this file interfaces with classes in org.jacoco.core.internal. Changes to this part should not be
+// necessary unless JaCoCo is updated or the way we instrument for coverage changes fundamentally.
+
+ /**
+ * A [ProbeInserter] that injects the bytecode instrumentation returned by [instrumentControlFlowEdge] and modifies
+ * the stack size and number of local variables accordingly.
+ */
+ private inner class EdgeCoverageProbeInserter(
+ access: Int,
+ name: String,
+ desc: String,
+ mv: MethodVisitor,
+ arrayStrategy: IProbeArrayStrategy,
+ ) : ProbeInserter(access, name, desc, mv, arrayStrategy) {
+ override fun insertProbe(id: Int) {
+ instrumentControlFlowEdge(mv, id, variable)
+ }
+
+ override fun visitMaxs(maxStack: Int, maxLocals: Int) {
+ val newMaxStack = max(maxStack + instrumentControlFlowEdgeStackSize, loadCoverageMapStackSize)
+ mv.visitMaxs(newMaxStack, maxLocals + 1)
+ }
+ }
+
+ private val edgeCoverageProbeInserterFactory =
+ IProbeInserterFactory { access, name, desc, mv, arrayStrategy ->
+ EdgeCoverageProbeInserter(access, name, desc, mv, arrayStrategy)
+ }
+
+ private inner class EdgeCoverageClassProbesAdapter(cv: ClassProbesVisitor, trackFrames: Boolean) :
+ ClassProbesAdapter(cv, trackFrames) {
+ override fun nextId(): Int = nextEdgeId()
+ }
+
+ private val edgeCoverageClassProbesAdapterFactory = IClassProbesAdapterFactory { probesVisitor, trackFrames ->
+ EdgeCoverageClassProbesAdapter(probesVisitor, trackFrames)
+ }
+
+ private val edgeCoverageProbeArrayStrategy = object : IProbeArrayStrategy {
+ override fun storeInstance(mv: MethodVisitor, clinit: Boolean, variable: Int): Int {
+ loadCoverageMap(mv, variable)
+ return loadCoverageMapStackSize
+ }
+
+ override fun addMembers(cv: ClassVisitor, probeCount: Int) {}
+ }
+
+ private fun MethodVisitor.push(value: Int) {
+ InstrSupport.push(this, value)
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt
new file mode 100644
index 00000000..92106e14
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Hook.kt
@@ -0,0 +1,119 @@
+// Copyright 2021 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.
+
+@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
+
+package com.code_intelligence.jazzer.instrumentor
+
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.MethodHook
+import com.code_intelligence.jazzer.api.MethodHooks
+import com.code_intelligence.jazzer.utils.descriptor
+import java.lang.invoke.MethodHandle
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+
+class Hook private constructor(hookMethod: Method, annotation: MethodHook) {
+ // Allowing arbitrary exterior whitespace in the target class name allows for an easy workaround
+ // for mangled hooks due to shading applied to hooks.
+ private val targetClassName = annotation.targetClassName.trim()
+ val targetMethodName = annotation.targetMethod
+ val targetMethodDescriptor = annotation.targetMethodDescriptor.takeIf { it.isNotEmpty() }
+ val hookType = annotation.type
+
+ val targetInternalClassName = targetClassName.replace('.', '/')
+ private val targetReturnTypeDescriptor = targetMethodDescriptor?.let { extractReturnTypeDescriptor(it) }
+ private val targetWrappedReturnTypeDescriptor = targetReturnTypeDescriptor?.let { getWrapperTypeDescriptor(it) }
+
+ private val hookClassName: String = hookMethod.declaringClass.name
+ val hookInternalClassName = hookClassName.replace('.', '/')
+ val hookMethodName: String = hookMethod.name
+ val hookMethodDescriptor = hookMethod.descriptor
+
+ override fun toString(): String {
+ return "$hookType $targetClassName.$targetMethodName: $hookClassName.$hookMethodName"
+ }
+
+ companion object {
+
+ fun verifyAndGetHook(hookMethod: Method, hookData: MethodHook): Hook {
+ // Verify the annotation type and extract information for debug statements.
+ val potentialHook = Hook(hookMethod, hookData)
+
+ // Verify the hook method's modifiers (public static).
+ require(Modifier.isPublic(hookMethod.modifiers)) { "$potentialHook: hook method must be public" }
+ require(Modifier.isStatic(hookMethod.modifiers)) { "$potentialHook: hook method must be static" }
+
+ // Verify the hook method's parameter count.
+ val numParameters = hookMethod.parameters.size
+ when (hookData.type) {
+ HookType.BEFORE, HookType.REPLACE -> require(numParameters == 4) { "$potentialHook: incorrect number of parameters (expected 4)" }
+ HookType.AFTER -> require(numParameters == 5) { "$potentialHook: incorrect number of parameters (expected 5)" }
+ }
+
+ // Verify the hook method's parameter types.
+ val parameterTypes = hookMethod.parameterTypes
+ require(parameterTypes[0] == MethodHandle::class.java) { "$potentialHook: first parameter must have type MethodHandle" }
+ require(parameterTypes[1] == Object::class.java || parameterTypes[1].name == potentialHook.targetClassName) { "$potentialHook: second parameter must have type Object or ${potentialHook.targetClassName}" }
+ require(parameterTypes[2] == Array<Object>::class.java) { "$potentialHook: third parameter must have type Object[]" }
+ require(parameterTypes[3] == Int::class.javaPrimitiveType) { "$potentialHook: fourth parameter must have type int" }
+
+ // Verify the hook method's return type if possible.
+ when (hookData.type) {
+ HookType.BEFORE, HookType.AFTER -> require(hookMethod.returnType == Void.TYPE) {
+ "$potentialHook: return type must be void"
+ }
+ HookType.REPLACE -> if (potentialHook.targetReturnTypeDescriptor != null) {
+ val returnTypeDescriptor = hookMethod.returnType.descriptor
+ if (potentialHook.targetReturnTypeDescriptor == "V") {
+ require(returnTypeDescriptor == "V") { "$potentialHook: return type must be void to match targetMethodDescriptor" }
+ } else {
+ require(
+ returnTypeDescriptor in listOf(
+ java.lang.Object::class.java.descriptor,
+ potentialHook.targetReturnTypeDescriptor,
+ potentialHook.targetWrappedReturnTypeDescriptor
+ )
+ ) {
+ "$potentialHook: return type must have type Object or match the descriptors ${potentialHook.targetReturnTypeDescriptor} or ${potentialHook.targetWrappedReturnTypeDescriptor}"
+ }
+ }
+ }
+ }
+
+ // AfterMethodHook only: Verify the type of the last parameter if known.
+ if (hookData.type == HookType.AFTER && potentialHook.targetReturnTypeDescriptor != null) {
+ require(
+ parameterTypes[4] == java.lang.Object::class.java ||
+ parameterTypes[4].descriptor == potentialHook.targetWrappedReturnTypeDescriptor
+ ) {
+ "$potentialHook: fifth parameter must have type Object or match the descriptor ${potentialHook.targetWrappedReturnTypeDescriptor}"
+ }
+ }
+
+ return potentialHook
+ }
+ }
+}
+
+fun loadHooks(hookClass: Class<*>): List<Hook> {
+ val hooks = mutableListOf<Hook>()
+ for (method in hookClass.methods) {
+ method.getAnnotation(MethodHook::class.java)?.let { hooks.add(Hook.verifyAndGetHook(method, it)) }
+ method.getAnnotation(MethodHooks::class.java)?.let {
+ it.value.forEach { hookAnnotation -> hooks.add(Hook.verifyAndGetHook(method, hookAnnotation)) }
+ }
+ }
+ return hooks
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt
new file mode 100644
index 00000000..ac5f1780
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookInstrumentor.kt
@@ -0,0 +1,48 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassVisitor
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter
+import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor
+
+internal class HookInstrumentor(private val hooks: Iterable<Hook>, private val java6Mode: Boolean) : Instrumentor {
+
+ private lateinit var random: DeterministicRandom
+
+ override fun instrument(bytecode: ByteArray): ByteArray {
+ val reader = ClassReader(bytecode)
+ val writer = ClassWriter(reader, ClassWriter.COMPUTE_MAXS)
+ random = DeterministicRandom("hook", reader.className)
+ val interceptor = object : ClassVisitor(Instrumentor.ASM_API_VERSION, writer) {
+ override fun visitMethod(
+ access: Int,
+ name: String?,
+ descriptor: String?,
+ signature: String?,
+ exceptions: Array<String>?,
+ ): MethodVisitor? {
+ val mv = cv.visitMethod(access, name, descriptor, signature, exceptions) ?: return null
+ return if (shouldInstrument(access))
+ makeHookMethodVisitor(access, descriptor, mv, hooks, java6Mode, random)
+ else
+ mv
+ }
+ }
+ reader.accept(interceptor, ClassReader.EXPAND_FRAMES)
+ return writer.toByteArray()
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
new file mode 100644
index 00000000..7c23c703
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/HookMethodVisitor.kt
@@ -0,0 +1,386 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Handle
+import com.code_intelligence.jazzer.third_party.objectweb.asm.MethodVisitor
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Type
+import com.code_intelligence.jazzer.third_party.objectweb.asm.commons.LocalVariablesSorter
+
+internal fun makeHookMethodVisitor(
+ access: Int,
+ descriptor: String?,
+ methodVisitor: MethodVisitor?,
+ hooks: Iterable<Hook>,
+ java6Mode: Boolean,
+ random: DeterministicRandom,
+): MethodVisitor {
+ return HookMethodVisitor(access, descriptor, methodVisitor, hooks, java6Mode, random).lvs
+}
+
+private class HookMethodVisitor(
+ access: Int,
+ descriptor: String?,
+ methodVisitor: MethodVisitor?,
+ hooks: Iterable<Hook>,
+ private val java6Mode: Boolean,
+ private val random: DeterministicRandom,
+) : MethodVisitor(Instrumentor.ASM_API_VERSION, methodVisitor) {
+
+ val lvs = object : LocalVariablesSorter(Instrumentor.ASM_API_VERSION, access, descriptor, this) {
+ override fun updateNewLocals(newLocals: Array<Any>) {
+ // The local variables involved in calling hooks do not need to outlive the current
+ // basic block and should thus not appear in stack map frames. By requesting the
+ // LocalVariableSorter to fill their entries in stack map frames with TOP, they will
+ // be treated like an unused local variable slot.
+ newLocals.fill(Opcodes.TOP)
+ }
+ }
+
+ private val hooks = hooks.associateBy { hook ->
+ var hookKey = "${hook.hookType}#${hook.targetInternalClassName}#${hook.targetMethodName}"
+ if (hook.targetMethodDescriptor != null)
+ hookKey += "#${hook.targetMethodDescriptor}"
+ hookKey
+ }
+
+ override fun visitMethodInsn(
+ opcode: Int,
+ owner: String,
+ methodName: String,
+ methodDescriptor: String,
+ isInterface: Boolean,
+ ) {
+ if (!isMethodInvocationOp(opcode)) {
+ mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
+ return
+ }
+ handleMethodInsn(HookType.BEFORE, opcode, owner, methodName, methodDescriptor, isInterface)
+ }
+
+ /**
+ * Emits the bytecode for a method call instruction for the next applicable hook type in order (BEFORE, REPLACE,
+ * AFTER). Since the instrumented code is indistinguishable from an uninstrumented call instruction, it can be
+ * safely nested. Combining REPLACE hooks with other hooks is however not supported as these hooks already subsume
+ * the functionality of BEFORE and AFTER hooks.
+ */
+ private fun visitNextHookTypeOrCall(
+ hookType: HookType,
+ appliedHook: Boolean,
+ opcode: Int,
+ owner: String,
+ methodName: String,
+ methodDescriptor: String,
+ isInterface: Boolean,
+ ) = when (hookType) {
+ HookType.BEFORE -> {
+ val nextHookType = if (appliedHook) {
+ // After a BEFORE hook has been applied, we can safely apply an AFTER hook by replacing the actual
+ // call instruction with the full bytecode injected for the AFTER hook.
+ HookType.AFTER
+ } else {
+ // If no BEFORE hook is registered, look for a REPLACE hook next.
+ HookType.REPLACE
+ }
+ handleMethodInsn(nextHookType, opcode, owner, methodName, methodDescriptor, isInterface)
+ }
+ HookType.REPLACE -> {
+ // REPLACE hooks can't (and don't need to) be mixed with other hooks. We only cycle through them if we
+ // couldn't find a matching REPLACE hook, in which case we try an AFTER hook next.
+ require(!appliedHook)
+ handleMethodInsn(HookType.AFTER, opcode, owner, methodName, methodDescriptor, isInterface)
+ }
+ // An AFTER hook is always the last in the chain. Whether a hook has been applied or not, always emit the
+ // actual call instruction.
+ HookType.AFTER -> mv.visitMethodInsn(opcode, owner, methodName, methodDescriptor, isInterface)
+ }
+
+ fun handleMethodInsn(
+ hookType: HookType,
+ opcode: Int,
+ owner: String,
+ methodName: String,
+ methodDescriptor: String,
+ isInterface: Boolean,
+ ) {
+ val hook = findMatchingHook(hookType, owner, methodName, methodDescriptor)
+ if (hook == null) {
+ visitNextHookTypeOrCall(hookType, false, opcode, owner, methodName, methodDescriptor, isInterface)
+ return
+ }
+
+ // The hookId is used to identify a call site.
+ val hookId = random.nextInt()
+
+ val paramDescriptors = extractParameterTypeDescriptors(methodDescriptor)
+ val localObjArr = storeMethodArguments(paramDescriptors)
+ // If the method we're hooking is not static there is now a reference to
+ // the object the method was invoked on at the top of the stack.
+ // If the method is static, that object is missing. We make up for it by pushing a null ref.
+ if (opcode == Opcodes.INVOKESTATIC) {
+ mv.visitInsn(Opcodes.ACONST_NULL)
+ }
+
+ // Save the owner object to a new local variable
+ val ownerDescriptor = "L$owner;"
+ val localOwnerObj = lvs.newLocal(Type.getType(ownerDescriptor))
+ mv.visitVarInsn(Opcodes.ASTORE, localOwnerObj) // consume objectref
+ // We now removed all values for the original method call from the operand stack
+ // and saved them to local variables.
+
+ // Start to build the arguments for the hook method.
+ if (methodName == "<init>") {
+ // Special case for constructors:
+ // We cannot create a MethodHandle for a constructor, so we push null instead.
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ // Only pass the this object if it has been initialized by the time the hook is invoked.
+ if (hook.hookType == HookType.AFTER) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
+ } else {
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ }
+ } else {
+ // Push a MethodHandle representing the hooked method.
+ val handleOpcode = when (opcode) {
+ Opcodes.INVOKEVIRTUAL -> Opcodes.H_INVOKEVIRTUAL
+ Opcodes.INVOKEINTERFACE -> Opcodes.H_INVOKEINTERFACE
+ Opcodes.INVOKESTATIC -> Opcodes.H_INVOKESTATIC
+ Opcodes.INVOKESPECIAL -> Opcodes.H_INVOKESPECIAL
+ else -> -1
+ }
+ if (java6Mode) {
+ // MethodHandle constants (type 15) are not supported in Java 6 class files (major version 50).
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ } else {
+ mv.visitLdcInsn(
+ Handle(
+ handleOpcode,
+ owner,
+ methodName,
+ methodDescriptor,
+ isInterface
+ )
+ ) // push MethodHandle
+ }
+ // Stack layout: ... | MethodHandle (objectref)
+ // Push the owner object again
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj)
+ }
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref)
+ // Push a reference to our object array with the saved arguments
+ mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref)
+ // Push the hook id
+ mv.visitLdcInsn(hookId)
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // How we proceed depends on the type of hook we want to implement
+ when (hook.hookType) {
+ HookType.BEFORE -> {
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+ // Stack layout: ...
+ // Push the values for the original method call onto the stack again
+ if (opcode != Opcodes.INVOKESTATIC) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
+ }
+ loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
+ // Stack layout: ... | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
+ // Call the original method or the next hook in order.
+ visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface)
+ }
+ HookType.REPLACE -> {
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+ // Stack layout: ... | [return value (primitive/objectref)]
+ // Check if we need to process the return value
+ val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
+ if (returnTypeDescriptor != "V") {
+ val hookMethodReturnType = extractReturnTypeDescriptor(hook.hookMethodDescriptor)
+ // if the hook method's return type is primitive we don't need to unwrap or cast it
+ if (!isPrimitiveType(hookMethodReturnType)) {
+ // Check if the returned object type is different than the one that should be returned
+ // If a primitive should be returned we check it's wrapper type
+ val expectedType = getWrapperTypeDescriptor(returnTypeDescriptor)
+ if (expectedType != hookMethodReturnType) {
+ // Cast object
+ mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(expectedType))
+ }
+ // Check if we need to unwrap the returned object
+ unwrapTypeIfPrimitive(returnTypeDescriptor)
+ }
+ }
+ }
+ HookType.AFTER -> {
+ // Push the values for the original method call again onto the stack
+ if (opcode != Opcodes.INVOKESTATIC) {
+ mv.visitVarInsn(Opcodes.ALOAD, localOwnerObj) // push owner object
+ }
+ loadMethodArguments(paramDescriptors, localObjArr) // push all method arguments
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // | [owner (objectref)] | arg1 (primitive/objectref) | arg2 (primitive/objectref) | ...
+ // Call the original method or the next hook in order
+ visitNextHookTypeOrCall(hookType, true, opcode, owner, methodName, methodDescriptor, isInterface)
+ val returnTypeDescriptor = extractReturnTypeDescriptor(methodDescriptor)
+ if (returnTypeDescriptor == "V") {
+ // If the method didn't return anything, we push a nullref as placeholder
+ mv.visitInsn(Opcodes.ACONST_NULL) // push nullref
+ }
+ // Wrap return value if it is a primitive type
+ wrapTypeIfPrimitive(returnTypeDescriptor)
+ // Stack layout: ... | MethodHandle (objectref) | owner (objectref) | object array (arrayref) | hookId (int)
+ // | return value (objectref)
+ // Store the result value in a local variable (but keep it on the stack)
+ val localReturnObj = lvs.newLocal(Type.getType(getWrapperTypeDescriptor(returnTypeDescriptor)))
+ mv.visitVarInsn(Opcodes.ASTORE, localReturnObj) // consume objectref
+ mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
+ // Call the hook method
+ mv.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ hook.hookInternalClassName,
+ hook.hookMethodName,
+ hook.hookMethodDescriptor,
+ false
+ )
+ // Stack layout: ...
+ if (returnTypeDescriptor != "V") {
+ // Push the return value again
+ mv.visitVarInsn(Opcodes.ALOAD, localReturnObj) // push objectref
+ // Unwrap it, if it was a primitive value
+ unwrapTypeIfPrimitive(returnTypeDescriptor)
+ // Stack layout: ... | return value (primitive/objectref)
+ }
+ }
+ }
+ }
+
+ private fun isMethodInvocationOp(opcode: Int) = opcode in listOf(
+ Opcodes.INVOKEVIRTUAL,
+ Opcodes.INVOKEINTERFACE,
+ Opcodes.INVOKESTATIC,
+ Opcodes.INVOKESPECIAL
+ )
+
+ private fun findMatchingHook(hookType: HookType, owner: String, name: String, descriptor: String): Hook? {
+ val withoutDescriptorKey = "$hookType#$owner#$name"
+ val withDescriptorKey = "$withoutDescriptorKey#$descriptor"
+ return hooks[withDescriptorKey] ?: hooks[withoutDescriptorKey]
+ }
+
+ // Stores all arguments for a method call in a local object array.
+ // paramDescriptors: The type descriptors for all method arguments
+ private fun storeMethodArguments(paramDescriptors: List<String>): Int {
+ // Allocate a new Object[] for the methods parameters.
+ mv.visitIntInsn(Opcodes.SIPUSH, paramDescriptors.size)
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object")
+ val localObjArr = lvs.newLocal(Type.getType("[Ljava/lang/Object;"))
+ mv.visitVarInsn(Opcodes.ASTORE, localObjArr)
+
+ // Loop over all arguments in reverse order (because the last argument is on top).
+ for ((argIdx, argDescriptor) in paramDescriptors.withIndex().reversed()) {
+ // If the argument is a primitive type, wrap it in it's wrapper class
+ wrapTypeIfPrimitive(argDescriptor)
+ // Store the argument in our object array, for that we need to shape the stack first.
+ // Stack layout: ... | method argument (objectref)
+ mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
+ // Stack layout: ... | method argument (objectref) | object array (arrayref)
+ mv.visitInsn(Opcodes.SWAP)
+ // Stack layout: ... | object array (arrayref) | method argument (objectref)
+ mv.visitIntInsn(Opcodes.SIPUSH, argIdx)
+ // Stack layout: ... | object array (arrayref) | method argument (objectref) | argument index (int)
+ mv.visitInsn(Opcodes.SWAP)
+ // Stack layout: ... | object array (arrayref) | argument index (int) | method argument (objectref)
+ mv.visitInsn(Opcodes.AASTORE) // consume all three: arrayref, index, value
+ // Stack layout: ...
+ // Continue with the remaining method arguments
+ }
+
+ // Return a reference to the array with the parameters.
+ return localObjArr
+ }
+
+ // Loads all arguments for a method call from a local object array.
+ // argTypeSigs: The type signatures for all method arguments
+ // localObjArr: Index of a local variable containing an object array where the arguments will be loaded from
+ private fun loadMethodArguments(paramDescriptors: List<String>, localObjArr: Int) {
+ // Loop over all arguments
+ for ((argIdx, argDescriptor) in paramDescriptors.withIndex()) {
+ // Push a reference to the object array on the stack
+ mv.visitVarInsn(Opcodes.ALOAD, localObjArr)
+ // Stack layout: ... | object array (arrayref)
+ // Push the index of the current argument on the stack
+ mv.visitIntInsn(Opcodes.SIPUSH, argIdx)
+ // Stack layout: ... | object array (arrayref) | argument index (int)
+ // Load the argument from the array
+ mv.visitInsn(Opcodes.AALOAD)
+ // Stack layout: ... | method argument (objectref)
+ // Cast object to it's original type (or it's wrapper object)
+ val wrapperTypeDescriptor = getWrapperTypeDescriptor(argDescriptor)
+ mv.visitTypeInsn(Opcodes.CHECKCAST, extractInternalClassName(wrapperTypeDescriptor))
+ // If the argument is a supposed to be a primitive type, unwrap the wrapped type
+ unwrapTypeIfPrimitive(argDescriptor)
+ // Stack layout: ... | method argument (primitive/objectref)
+ // Continue with the remaining method arguments
+ }
+ }
+
+ // Removes a primitive value from the top of the operand stack
+ // and pushes it enclosed in it's wrapper type (e.g. removes int, pushes Integer).
+ // This is done by calling .valueOf(...) on the wrapper class.
+ private fun wrapTypeIfPrimitive(unwrappedTypeDescriptor: String) {
+ if (!isPrimitiveType(unwrappedTypeDescriptor) || unwrappedTypeDescriptor == "V") return
+ val wrapperTypeDescriptor = getWrapperTypeDescriptor(unwrappedTypeDescriptor)
+ val wrapperType = extractInternalClassName(wrapperTypeDescriptor)
+ val valueOfDescriptor = "($unwrappedTypeDescriptor)$wrapperTypeDescriptor"
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapperType, "valueOf", valueOfDescriptor, false)
+ }
+
+ // Removes a wrapper object around a given primitive type from the top of the operand stack
+ // and pushes the primitive value it contains (e.g. removes Integer, pushes int).
+ // This is done by calling .intValue(...) / .charValue(...) / ... on the wrapper object.
+ private fun unwrapTypeIfPrimitive(primitiveTypeDescriptor: String) {
+ val (methodName, wrappedTypeDescriptor) = when (primitiveTypeDescriptor) {
+ "B" -> Pair("byteValue", "java/lang/Byte")
+ "C" -> Pair("charValue", "java/lang/Character")
+ "D" -> Pair("doubleValue", "java/lang/Double")
+ "F" -> Pair("floatValue", "java/lang/Float")
+ "I" -> Pair("intValue", "java/lang/Integer")
+ "J" -> Pair("longValue", "java/lang/Long")
+ "S" -> Pair("shortValue", "java/lang/Short")
+ "Z" -> Pair("booleanValue", "java/lang/Boolean")
+ else -> return
+ }
+ mv.visitMethodInsn(
+ Opcodes.INVOKEVIRTUAL,
+ wrappedTypeDescriptor,
+ methodName,
+ "()$primitiveTypeDescriptor",
+ false
+ )
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt
new file mode 100644
index 00000000..86ad45a3
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/Instrumentor.kt
@@ -0,0 +1,45 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodNode
+
+enum class InstrumentationType {
+ CMP,
+ COV,
+ DIV,
+ GEP,
+ INDIR,
+ NATIVE,
+}
+
+internal interface Instrumentor {
+ fun instrument(bytecode: ByteArray): ByteArray
+
+ fun shouldInstrument(access: Int): Boolean {
+ return (access and Opcodes.ACC_ABSTRACT == 0) &&
+ (access and Opcodes.ACC_NATIVE == 0)
+ }
+
+ fun shouldInstrument(method: MethodNode): Boolean {
+ return shouldInstrument(method.access) &&
+ method.instructions.size() > 0
+ }
+
+ companion object {
+ const val ASM_API_VERSION = Opcodes.ASM9
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt
new file mode 100644
index 00000000..e6d3176e
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentor.kt
@@ -0,0 +1,258 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.runtime.TraceDataFlowNativeCallbacks
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassReader
+import com.code_intelligence.jazzer.third_party.objectweb.asm.ClassWriter
+import com.code_intelligence.jazzer.third_party.objectweb.asm.Opcodes
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.AbstractInsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.ClassNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.InsnList
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.InsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.IntInsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.LdcInsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.LookupSwitchInsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodInsnNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.MethodNode
+import com.code_intelligence.jazzer.third_party.objectweb.asm.tree.TableSwitchInsnNode
+
+internal class TraceDataFlowInstrumentor(private val types: Set<InstrumentationType>, callbackClass: Class<*> = TraceDataFlowNativeCallbacks::class.java) : Instrumentor {
+
+ private val callbackInternalClassName = callbackClass.name.replace('.', '/')
+ private lateinit var random: DeterministicRandom
+
+ override fun instrument(bytecode: ByteArray): ByteArray {
+ val node = ClassNode()
+ val reader = ClassReader(bytecode)
+ reader.accept(node, 0)
+ random = DeterministicRandom("trace", node.name)
+ for (method in node.methods) {
+ if (shouldInstrument(method)) {
+ addDataFlowInstrumentation(method)
+ }
+ }
+
+ val writer = ClassWriter(ClassWriter.COMPUTE_MAXS)
+ node.accept(writer)
+ return writer.toByteArray()
+ }
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ private fun addDataFlowInstrumentation(method: MethodNode) {
+ loop@ for (inst in method.instructions.toArray()) {
+ when (inst.opcode) {
+ Opcodes.LCMP -> {
+ if (InstrumentationType.CMP !in types) continue@loop
+ method.instructions.insertBefore(inst, longCmpInstrumentation())
+ method.instructions.remove(inst)
+ }
+ Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE,
+ Opcodes.IF_ICMPLT, Opcodes.IF_ICMPLE,
+ Opcodes.IF_ICMPGT, Opcodes.IF_ICMPGE -> {
+ if (InstrumentationType.CMP !in types) continue@loop
+ method.instructions.insertBefore(inst, intCmpInstrumentation())
+ }
+ Opcodes.IFEQ, Opcodes.IFNE,
+ Opcodes.IFLT, Opcodes.IFLE,
+ Opcodes.IFGT, Opcodes.IFGE -> {
+ if (InstrumentationType.CMP !in types) continue@loop
+ // The IF* opcodes are often used to branch based on the result of a compare
+ // instruction for a type other than int. The operands of this compare will
+ // already be reported via the instrumentation above (for non-floating point
+ // numbers) and the follow-up compare does not provide a good signal as all
+ // operands will be in {-1, 0, 1}. Skip instrumentation for it.
+ if (inst.previous?.opcode in listOf(Opcodes.DCMPG, Opcodes.DCMPL, Opcodes.FCMPG, Opcodes.DCMPL) ||
+ (inst.previous as? MethodInsnNode)?.name == "traceCmpLongWrapper"
+ )
+ continue@loop
+ method.instructions.insertBefore(inst, ifInstrumentation())
+ }
+ Opcodes.LOOKUPSWITCH, Opcodes.TABLESWITCH -> {
+ if (InstrumentationType.CMP !in types) continue@loop
+ // Mimic the exclusion logic for small label values in libFuzzer:
+ // https://github.com/llvm-mirror/compiler-rt/blob/69445f095c22aac2388f939bedebf224a6efcdaf/lib/fuzzer/FuzzerTracePC.cpp#L520
+ // Case values are reported to libFuzzer via an array of unsigned long values and thus need to be
+ // sorted by unsigned value.
+ val caseValues = when (inst) {
+ is LookupSwitchInsnNode -> {
+ if (inst.keys.isEmpty() || (0 <= inst.keys.first() && inst.keys.last() < 256))
+ continue@loop
+ inst.keys
+ }
+ is TableSwitchInsnNode -> {
+ if (0 <= inst.min && inst.max < 256)
+ continue@loop
+ (inst.min..inst.max).filter { caseValue ->
+ val index = caseValue - inst.min
+ // Filter out "gap cases".
+ inst.labels[index].label != inst.dflt.label
+ }.toList()
+ }
+ // Not reached.
+ else -> continue@loop
+ }.sortedBy { it.toUInt() }.map { it.toLong() }.toLongArray()
+ method.instructions.insertBefore(inst, switchInstrumentation(caseValues))
+ }
+ Opcodes.IDIV -> {
+ if (InstrumentationType.DIV !in types) continue@loop
+ method.instructions.insertBefore(inst, intDivInstrumentation())
+ }
+ Opcodes.LDIV -> {
+ if (InstrumentationType.DIV !in types) continue@loop
+ method.instructions.insertBefore(inst, longDivInstrumentation())
+ }
+ Opcodes.AALOAD, Opcodes.BALOAD,
+ Opcodes.CALOAD, Opcodes.DALOAD,
+ Opcodes.FALOAD, Opcodes.IALOAD,
+ Opcodes.LALOAD, Opcodes.SALOAD -> {
+ if (InstrumentationType.GEP !in types) continue@loop
+ if (!isConstantIntegerPushInsn(inst.previous)) continue@loop
+ method.instructions.insertBefore(inst, gepLoadInstrumentation())
+ }
+ Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL, Opcodes.INVOKESTATIC, Opcodes.INVOKEVIRTUAL -> {
+ if (InstrumentationType.GEP !in types) continue@loop
+ if (!isGepLoadMethodInsn(inst as MethodInsnNode)) continue@loop
+ if (!isConstantIntegerPushInsn(inst.previous)) continue@loop
+ method.instructions.insertBefore(inst, gepLoadInstrumentation())
+ }
+ }
+ }
+ }
+
+ private fun InsnList.pushFakePc() {
+ add(LdcInsnNode(random.nextInt(4096)))
+ }
+
+ private fun longCmpInstrumentation() = InsnList().apply {
+ pushFakePc()
+ // traceCmpLong returns the result of the comparison as duplicating two longs on the stack
+ // is not possible without local variables.
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpLongWrapper", "(JJI)I", false))
+ }
+
+ private fun intCmpInstrumentation() = InsnList().apply {
+ add(InsnNode(Opcodes.DUP2))
+ pushFakePc()
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceCmpInt", "(III)V", false))
+ }
+
+ private fun ifInstrumentation() = InsnList().apply {
+ add(InsnNode(Opcodes.DUP))
+ // All if* instructions are compares to the constant 0.
+ add(InsnNode(Opcodes.ICONST_0))
+ add(InsnNode(Opcodes.SWAP))
+ pushFakePc()
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceConstCmpInt", "(III)V", false))
+ }
+
+ private fun intDivInstrumentation() = InsnList().apply {
+ add(InsnNode(Opcodes.DUP))
+ pushFakePc()
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivInt", "(II)V", false))
+ }
+
+ private fun longDivInstrumentation() = InsnList().apply {
+ add(InsnNode(Opcodes.DUP2))
+ pushFakePc()
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceDivLong", "(JI)V", false))
+ }
+
+ private fun switchInstrumentation(caseValues: LongArray) = InsnList().apply {
+ // duplicate {lookup,table}switch key for use as first function argument
+ add(InsnNode(Opcodes.DUP))
+ add(InsnNode(Opcodes.I2L))
+ // Set up array with switch case values. The format libfuzzer expects is created here directly, i.e., the first
+ // two entries are the number of cases and the bit size of values (always 32).
+ add(IntInsnNode(Opcodes.SIPUSH, caseValues.size + 2))
+ add(IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_LONG))
+ // Store number of cases
+ add(InsnNode(Opcodes.DUP))
+ add(IntInsnNode(Opcodes.SIPUSH, 0))
+ add(LdcInsnNode(caseValues.size.toLong()))
+ add(InsnNode(Opcodes.LASTORE))
+ // Store bit size of keys
+ add(InsnNode(Opcodes.DUP))
+ add(IntInsnNode(Opcodes.SIPUSH, 1))
+ add(LdcInsnNode(32.toLong()))
+ add(InsnNode(Opcodes.LASTORE))
+ // Store {lookup,table}switch case values
+ for ((i, caseValue) in caseValues.withIndex()) {
+ add(InsnNode(Opcodes.DUP))
+ add(IntInsnNode(Opcodes.SIPUSH, 2 + i))
+ add(LdcInsnNode(caseValue))
+ add(InsnNode(Opcodes.LASTORE))
+ }
+ pushFakePc()
+ // call the native callback function
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceSwitch", "(J[JI)V", false))
+ }
+
+ /**
+ * Returns true if [node] represents an instruction that possibly pushes a valid, non-zero, constant array index
+ * onto the stack.
+ */
+ private fun isConstantIntegerPushInsn(node: AbstractInsnNode?) = node?.opcode in CONSTANT_INTEGER_PUSH_OPCODES
+
+ /**
+ * Returns true if [node] represents a call to a method that performs an indexed lookup into an array-like
+ * structure.
+ */
+ private fun isGepLoadMethodInsn(node: MethodInsnNode): Boolean {
+ if (!node.desc.startsWith("(I)")) return false
+ val returnType = node.desc.removePrefix("(I)")
+ return MethodInfo(node.owner, node.name, returnType) in GEP_LOAD_METHODS
+ }
+
+ private fun gepLoadInstrumentation() = InsnList().apply {
+ // Duplicate the index and convert to long.
+ add(InsnNode(Opcodes.DUP))
+ add(InsnNode(Opcodes.I2L))
+ pushFakePc()
+ add(MethodInsnNode(Opcodes.INVOKESTATIC, callbackInternalClassName, "traceGep", "(JI)V", false))
+ }
+
+ companion object {
+ // Low constants (0, 1) are omitted as they create a lot of noise.
+ val CONSTANT_INTEGER_PUSH_OPCODES = listOf(
+ Opcodes.BIPUSH, Opcodes.SIPUSH,
+ Opcodes.LDC,
+ Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5
+ )
+
+ data class MethodInfo(val internalClassName: String, val name: String, val returnType: String)
+
+ val GEP_LOAD_METHODS = setOf(
+ MethodInfo("java/util/AbstractList", "get", "Ljava/lang/Object;"),
+ MethodInfo("java/util/ArrayList", "get", "Ljava/lang/Object;"),
+ MethodInfo("java/util/List", "get", "Ljava/lang/Object;"),
+ MethodInfo("java/util/Stack", "get", "Ljava/lang/Object;"),
+ MethodInfo("java/util/Vector", "get", "Ljava/lang/Object;"),
+ MethodInfo("java/lang/CharSequence", "charAt", "C"),
+ MethodInfo("java/lang/String", "charAt", "C"),
+ MethodInfo("java/lang/StringBuffer", "charAt", "C"),
+ MethodInfo("java/lang/StringBuilder", "charAt", "C"),
+ MethodInfo("java/lang/String", "codePointAt", "I"),
+ MethodInfo("java/lang/String", "codePointBefore", "I"),
+ MethodInfo("java/nio/ByteBuffer", "get", "B"),
+ MethodInfo("java/nio/ByteBuffer", "getChar", "C"),
+ MethodInfo("java/nio/ByteBuffer", "getDouble", "D"),
+ MethodInfo("java/nio/ByteBuffer", "getFloat", "F"),
+ MethodInfo("java/nio/ByteBuffer", "getInt", "I"),
+ MethodInfo("java/nio/ByteBuffer", "getLong", "J"),
+ MethodInfo("java/nio/ByteBuffer", "getShort", "S"),
+ )
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules
new file mode 100644
index 00000000..c2092b3b
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/instrumentor/shade_rules
@@ -0,0 +1 @@
+rule org.** com.code_intelligence.jazzer.third_party.@1 \ No newline at end of file
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel
new file mode 100644
index 00000000..df28adb4
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
+
+java_jni_library(
+ name = "replay",
+ srcs = ["Replayer.java"],
+ native_libs = ["//agent/src/main/native/com/code_intelligence/jazzer/replay"],
+ visibility = ["//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime:fuzzed_data_provider",
+ ],
+)
+
+java_binary(
+ name = "Replayer",
+ visibility = ["//visibility:public"],
+ runtime_deps = [":replay"],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java
new file mode 100644
index 00000000..fc6bfc4f
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/replay/Replayer.java
@@ -0,0 +1,159 @@
+// Copyright 2021 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.replay;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.code_intelligence.jazzer.runtime.FuzzedDataProviderImpl;
+import com.github.fmeum.rules_jni.RulesJni;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+public class Replayer {
+ public static final int STATUS_FINDING = 77;
+ public static final int STATUS_OTHER_ERROR = 1;
+
+ static {
+ try {
+ RulesJni.loadLibrary("replay", Replayer.class);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ System.exit(STATUS_OTHER_ERROR);
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.err.println("Usage: <fuzz target class> <input file path> <fuzzerInitialize args>...");
+ System.exit(STATUS_OTHER_ERROR);
+ }
+ ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
+
+ Class<?> fuzzTargetClass;
+ try {
+ fuzzTargetClass = Class.forName(args[0]);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ System.exit(STATUS_OTHER_ERROR);
+ // Without this return the compiler sees fuzzTargetClass as possibly uninitialized.
+ return;
+ }
+
+ String inputFilePath = args[1];
+ byte[] input = loadInput(inputFilePath);
+
+ String[] fuzzTargetArgs = Arrays.copyOfRange(args, 2, args.length);
+ executeFuzzerInitialize(fuzzTargetClass, fuzzTargetArgs);
+ executeFuzzTarget(fuzzTargetClass, input);
+ }
+
+ private static byte[] loadInput(String inputFilePath) {
+ try {
+ return Files.readAllBytes(Paths.get(inputFilePath));
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.exit(STATUS_OTHER_ERROR);
+ // Without this return the compiler sees loadInput as possibly not returning a value.
+ return null;
+ }
+ }
+
+ private static void executeFuzzerInitialize(Class<?> fuzzTarget, String[] args) {
+ // public static void fuzzerInitialize()
+ try {
+ Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize");
+ fuzzerInitialize.invoke(null);
+ return;
+ } catch (Exception e) {
+ handleInvokeException(e, fuzzTarget);
+ }
+ // public static void fuzzerInitialize(String[] args)
+ try {
+ Method fuzzerInitialize = fuzzTarget.getMethod("fuzzerInitialize", String[].class);
+ fuzzerInitialize.invoke(null, (Object) args);
+ } catch (Exception e) {
+ handleInvokeException(e, fuzzTarget);
+ }
+ }
+
+ public static void executeFuzzTarget(Class<?> fuzzTarget, byte[] input) {
+ // public static void fuzzerTestOneInput(byte[] input)
+ try {
+ Method fuzzerTestOneInput = fuzzTarget.getMethod("fuzzerTestOneInput", byte[].class);
+ fuzzerTestOneInput.invoke(null, (Object) input);
+ return;
+ } catch (Exception e) {
+ handleInvokeException(e, fuzzTarget);
+ }
+ // public static void fuzzerTestOneInput(FuzzedDataProvider data)
+ try {
+ Method fuzzerTestOneInput =
+ fuzzTarget.getMethod("fuzzerTestOneInput", FuzzedDataProvider.class);
+ fuzzerTestOneInput.invoke(null, makeFuzzedDataProvider(input));
+ return;
+ } catch (Exception e) {
+ handleInvokeException(e, fuzzTarget);
+ }
+ System.err.printf("%s must define exactly one of the following two functions:%n"
+ + " public static void fuzzerTestOneInput(byte[] ...)%n"
+ + " public static void fuzzerTestOneInput(FuzzedDataProvider ...)%n"
+ + "Note: Fuzz targets returning boolean are no longer supported; exceptions should%n"
+ + "be thrown instead of returning true.%n",
+ fuzzTarget.getName());
+ System.exit(STATUS_OTHER_ERROR);
+ }
+
+ private static void handleInvokeException(Exception e, Class<?> fuzzTarget) {
+ if (e instanceof NoSuchMethodException)
+ return;
+ if (e instanceof InvocationTargetException) {
+ filterOutOwnStackTraceElements(e.getCause(), fuzzTarget);
+ e.getCause().printStackTrace();
+ System.exit(STATUS_FINDING);
+ } else {
+ e.printStackTrace();
+ System.exit(STATUS_OTHER_ERROR);
+ }
+ }
+
+ private static void filterOutOwnStackTraceElements(Throwable t, Class<?> fuzzTarget) {
+ if (t.getCause() != null)
+ filterOutOwnStackTraceElements(t.getCause(), fuzzTarget);
+ if (t.getStackTrace() == null || t.getStackTrace().length == 0)
+ return;
+ StackTraceElement lowestFrame = t.getStackTrace()[t.getStackTrace().length - 1];
+ if (!lowestFrame.getClassName().equals(Replayer.class.getName())
+ || !lowestFrame.getMethodName().equals("main"))
+ return;
+ for (int i = t.getStackTrace().length - 1; i >= 0; i--) {
+ StackTraceElement frame = t.getStackTrace()[i];
+ if (frame.getClassName().equals(fuzzTarget.getName())
+ && frame.getMethodName().equals("fuzzerTestOneInput")) {
+ t.setStackTrace(Arrays.copyOfRange(t.getStackTrace(), 0, i + 1));
+ break;
+ }
+ }
+ }
+
+ private static FuzzedDataProvider makeFuzzedDataProvider(byte[] input) {
+ feedFuzzedDataProvider(input);
+ return new FuzzedDataProviderImpl();
+ }
+
+ private static native void feedFuzzedDataProvider(byte[] input);
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
new file mode 100644
index 00000000..095b0bf8
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
@@ -0,0 +1,48 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+java_library(
+ name = "fuzzed_data_provider",
+ srcs = [
+ "FuzzedDataProviderImpl.java",
+ ],
+ visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ ],
+)
+
+java_library(
+ name = "signal_handler",
+ srcs = ["SignalHandler.java"],
+ javacopts = [
+ "-XDenableSunApiLintControl",
+ ],
+)
+
+kt_jvm_library(
+ name = "runtime",
+ srcs = [
+ "CoverageMap.java",
+ "ExceptionUtils.kt",
+ "HardToCatchError.java",
+ "JazzerInternal.java",
+ "ManifestUtils.kt",
+ "NativeLibHooks.java",
+ "RecordingFuzzedDataProvider.java",
+ "SignalHandler.java",
+ "TraceCmpHooks.java",
+ "TraceDataFlowNativeCallbacks.java",
+ "TraceDivHooks.java",
+ "TraceIndirHooks.java",
+ ],
+ visibility = ["//visibility:public"],
+ runtime_deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz",
+ ],
+ deps = [
+ ":fuzzed_data_provider",
+ ":signal_handler",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/utils",
+ ],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java
new file mode 100644
index 00000000..af2424a2
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java
@@ -0,0 +1,33 @@
+// Copyright 2021 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.runtime;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents the Java view on a libFuzzer 8 bit counter coverage map.
+ * By using a direct ByteBuffer, the counter array is shared directly with
+ * native code.
+ */
+final public class CoverageMap {
+ public static ByteBuffer mem = ByteBuffer.allocateDirect(0);
+
+ public static void enlargeCoverageMap() {
+ registerNewCoverageCounters();
+ System.out.println("INFO: New number of inline 8-bit counters: " + mem.capacity());
+ }
+
+ private static native void registerNewCoverageCounters();
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt
new file mode 100644
index 00000000..31a61740
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ExceptionUtils.kt
@@ -0,0 +1,166 @@
+// Copyright 2021 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.
+
+@file:JvmName("ExceptionUtils")
+
+package com.code_intelligence.jazzer.runtime
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow
+import java.lang.management.ManagementFactory
+import java.nio.ByteBuffer
+import java.security.MessageDigest
+
+private fun hash(throwable: Throwable, passToRootCause: Boolean): ByteArray =
+ MessageDigest.getInstance("SHA-256").run {
+ // It suffices to hash the stack trace of the deepest cause as the higher-level causes only
+ // contain part of the stack trace (plus possibly a different exception type).
+ var rootCause = throwable
+ if (passToRootCause) {
+ while (true) {
+ rootCause = rootCause.cause ?: break
+ }
+ }
+ update(rootCause.javaClass.name.toByteArray())
+ for (element in rootCause.stackTrace) {
+ update(element.toString().toByteArray())
+ }
+ if (throwable.suppressed.isNotEmpty()) {
+ update("suppressed".toByteArray())
+ for (suppressed in throwable.suppressed) {
+ update(hash(suppressed, passToRootCause))
+ }
+ }
+ digest()
+ }
+
+/**
+ * Computes a hash of the stack trace of [throwable] without messages.
+ *
+ * The hash can be used to deduplicate stack traces obtained on crashes. By not including the
+ * messages, this hash should not depend on the precise crashing input.
+ */
+fun computeDedupToken(throwable: Throwable): Long {
+ var passToRootCause = true
+ if (throwable is FuzzerSecurityIssueLow && throwable.cause is StackOverflowError) {
+ // Special handling for StackOverflowErrors as processed by preprocessThrowable:
+ // Only consider the repeated part of the stack trace and ignore the original stack trace in
+ // the cause.
+ passToRootCause = false
+ }
+ return ByteBuffer.wrap(hash(throwable, passToRootCause)).long
+}
+
+/**
+ * Annotates [throwable] with a severity and additional information if it represents a bug type
+ * that has security content.
+ */
+fun preprocessThrowable(throwable: Throwable): Throwable = when (throwable) {
+ is StackOverflowError -> {
+ // StackOverflowErrors are hard to deduplicate as the top-most stack frames vary wildly,
+ // whereas the information that is most useful for deduplication detection is hidden in the
+ // rest of the (truncated) stack frame.
+ // We heuristically clean up the stack trace by taking the elements from the bottom and
+ // stopping at the first repetition of a frame. The original error is returned as the cause
+ // unchanged.
+ val observedFrames = mutableSetOf<StackTraceElement>()
+ val bottomFramesWithoutRepetition = throwable.stackTrace.takeLastWhile { frame ->
+ (frame !in observedFrames).also { observedFrames.add(frame) }
+ }
+ FuzzerSecurityIssueLow("Stack overflow (use '${getReproducingXssArg()}' to reproduce)", throwable).apply {
+ stackTrace = bottomFramesWithoutRepetition.toTypedArray()
+ }
+ }
+ is OutOfMemoryError -> stripOwnStackTrace(
+ FuzzerSecurityIssueLow(
+ "Out of memory (use '${getReproducingXmxArg()}' to reproduce)", throwable
+ )
+ )
+ is VirtualMachineError -> stripOwnStackTrace(FuzzerSecurityIssueLow(throwable))
+ else -> throwable
+}
+
+/**
+ * Strips the stack trace of [throwable] (e.g. because it was created in a utility method), but not
+ * the stack traces of its causes.
+ */
+private fun stripOwnStackTrace(throwable: Throwable) = throwable.apply {
+ stackTrace = emptyArray()
+}
+
+/**
+ * Returns a valid `-Xmx` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
+ * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
+ */
+private fun getReproducingXmxArg(): String? {
+ val maxHeapSizeInMegaBytes = (getNumericFinalFlagValue("MaxHeapSize") ?: return null) shr 20
+ val conservativeMaxHeapSizeInMegaBytes = (maxHeapSizeInMegaBytes * 0.9).toInt()
+ return "-Xmx${conservativeMaxHeapSizeInMegaBytes}m"
+}
+
+/**
+ * Returns a valid `-Xss` JVM argument that sets the stack size to a value with which [StackOverflowError] findings can
+ * be reproduced, assuming the environment is sufficiently similar (e.g. OS and JVM version).
+ */
+private fun getReproducingXssArg(): String? {
+ val threadStackSizeInKiloBytes = getNumericFinalFlagValue("ThreadStackSize") ?: return null
+ val conservativeThreadStackSizeInKiloBytes = (threadStackSizeInKiloBytes * 0.9).toInt()
+ return "-Xss${conservativeThreadStackSizeInKiloBytes}k"
+}
+
+private fun getNumericFinalFlagValue(arg: String): Long? {
+ val argPattern = "$arg\\D*(\\d*)".toRegex()
+ return argPattern.find(javaFullFinalFlags ?: return null)?.groupValues?.get(1)?.toLongOrNull()
+}
+
+private val javaFullFinalFlags by lazy {
+ readJavaFullFinalFlags()
+}
+
+private fun readJavaFullFinalFlags(): String? {
+ val javaHome = System.getProperty("java.home") ?: return null
+ val javaBinary = "$javaHome/bin/java"
+ val currentJvmArgs = ManagementFactory.getRuntimeMXBean().inputArguments
+ val javaPrintFlagsProcess = ProcessBuilder(
+ listOf(javaBinary) + currentJvmArgs + listOf(
+ "-XX:+PrintFlagsFinal",
+ "-version"
+ )
+ ).start()
+ return javaPrintFlagsProcess.inputStream.bufferedReader().useLines { lineSequence ->
+ lineSequence
+ .filter { it.contains("ThreadStackSize") || it.contains("MaxHeapSize") }
+ .joinToString("\n")
+ }
+}
+
+fun dumpAllStackTraces() {
+ System.err.println("\nStack traces of all JVM threads:\n")
+ for ((thread, stack) in Thread.getAllStackTraces()) {
+ System.err.println(thread)
+ // Remove traces of this method and the methods it calls.
+ stack.asList()
+ .asReversed()
+ .takeWhile {
+ !(
+ it.className == "com.code_intelligence.jazzer.runtime.ExceptionUtils" &&
+ it.methodName == "dumpAllStackTraces"
+ )
+ }
+ .asReversed()
+ .forEach { frame ->
+ System.err.println("\tat $frame")
+ }
+ System.err.println()
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java
new file mode 100644
index 00000000..fe4d8ac7
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl.java
@@ -0,0 +1,83 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public class FuzzedDataProviderImpl implements FuzzedDataProvider {
+ public FuzzedDataProviderImpl() {}
+
+ @Override public native boolean consumeBoolean();
+
+ @Override public native boolean[] consumeBooleans(int maxLength);
+
+ @Override public native byte consumeByte();
+
+ @Override public native byte consumeByte(byte min, byte max);
+
+ @Override public native short consumeShort();
+
+ @Override public native short consumeShort(short min, short max);
+
+ @Override public native short[] consumeShorts(int maxLength);
+
+ @Override public native int consumeInt();
+
+ @Override public native int consumeInt(int min, int max);
+
+ @Override public native int[] consumeInts(int maxLength);
+
+ @Override public native long consumeLong();
+
+ @Override public native long consumeLong(long min, long max);
+
+ @Override public native long[] consumeLongs(int maxLength);
+
+ @Override public native float consumeFloat();
+
+ @Override public native float consumeRegularFloat();
+
+ @Override public native float consumeRegularFloat(float min, float max);
+
+ @Override public native float consumeProbabilityFloat();
+
+ @Override public native double consumeDouble();
+
+ @Override public native double consumeRegularDouble(double min, double max);
+
+ @Override public native double consumeRegularDouble();
+
+ @Override public native double consumeProbabilityDouble();
+
+ @Override public native char consumeChar();
+
+ @Override public native char consumeChar(char min, char max);
+
+ @Override public native char consumeCharNoSurrogates();
+
+ @Override public native String consumeAsciiString(int maxLength);
+
+ @Override public native String consumeString(int maxLength);
+
+ @Override public native String consumeRemainingAsAsciiString();
+
+ @Override public native String consumeRemainingAsString();
+
+ @Override public native byte[] consumeBytes(int maxLength);
+
+ @Override public native byte[] consumeRemainingAsBytes();
+
+ @Override public native int remainingBytes();
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java
new file mode 100644
index 00000000..cf136051
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/HardToCatchError.java
@@ -0,0 +1,82 @@
+// Copyright 2021 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.runtime;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * An Error that rethrows itself when any of its getters is invoked.
+ */
+public class HardToCatchError extends Error {
+ public HardToCatchError() {
+ super();
+ }
+
+ @Override
+ public String getMessage() {
+ throw this;
+ }
+
+ @Override
+ public String getLocalizedMessage() {
+ throw this;
+ }
+
+ @Override
+ public synchronized Throwable initCause(Throwable cause) {
+ throw this;
+ }
+
+ @Override
+ public String toString() {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace() {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace(PrintStream s) {
+ throw this;
+ }
+
+ @Override
+ public void printStackTrace(PrintWriter s) {
+ throw this;
+ }
+
+ @Override
+ public StackTraceElement[] getStackTrace() {
+ throw this;
+ }
+
+ @Override
+ public int hashCode() {
+ throw this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ throw this;
+ }
+
+ @Override
+ public Object clone() {
+ throw this;
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java
new file mode 100644
index 00000000..8bc1b38c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/JazzerInternal.java
@@ -0,0 +1,29 @@
+// Copyright 2021 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.runtime;
+
+final public class JazzerInternal {
+ // Accessed from native code.
+ private static Throwable lastFinding;
+
+ // Accessed from api.Jazzer via reflection.
+ public static void reportFindingFromHook(Throwable finding) {
+ lastFinding = finding;
+ // Throw an Error that is hard to catch (short of outright ignoring it) in order to quickly
+ // terminate the execution of the fuzz target. The finding will be reported as soon as the fuzz
+ // target returns even if this Error is swallowed.
+ throw new HardToCatchError();
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt
new file mode 100644
index 00000000..d88c3e18
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/ManifestUtils.kt
@@ -0,0 +1,54 @@
+// Copyright 2021 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.runtime
+
+import java.util.jar.Manifest
+
+object ManifestUtils {
+
+ const val FUZZ_TARGET_CLASS = "Jazzer-Fuzz-Target-Class"
+ const val HOOK_CLASSES = "Jazzer-Hook-Classes"
+
+ fun combineManifestValues(attribute: String): List<String> {
+ val manifests = ClassLoader.getSystemResources("META-INF/MANIFEST.MF")
+ return manifests.asSequence().mapNotNull { url ->
+ url.openStream().use { inputStream ->
+ val manifest = Manifest(inputStream)
+ manifest.mainAttributes.getValue(attribute)
+ }
+ }.toList()
+ }
+
+ /**
+ * Returns the value of the `Fuzz-Target-Class` manifest attribute if there is a unique one among all manifest
+ * files in the classpath.
+ */
+ @JvmStatic
+ fun detectFuzzTargetClass(): String? {
+ val fuzzTargets = combineManifestValues(FUZZ_TARGET_CLASS)
+ return when (fuzzTargets.size) {
+ 0 -> null
+ 1 -> fuzzTargets.first()
+ else -> {
+ println(
+ """
+ |WARN: More than one Jazzer-Fuzz-Target-Class manifest entry detected on the
+ |classpath.""".trimMargin()
+ )
+ null
+ }
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
new file mode 100644
index 00000000..495cad7c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
@@ -0,0 +1,35 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+@SuppressWarnings("unused")
+final public class NativeLibHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime",
+ targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System",
+ targetMethod = "loadLibrary", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime", targetMethod = "load",
+ targetMethodDescriptor = "(Ljava/lang/String;)V")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.System", targetMethod = "load",
+ targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static void
+ loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.handleLibraryLoad();
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java
new file mode 100644
index 00000000..976e024c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider.java
@@ -0,0 +1,74 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Base64;
+
+// Wraps the native FuzzedDataProviderImpl and serializes all its return values
+// into a Base64-encoded string.
+final class RecordingFuzzedDataProvider implements InvocationHandler {
+ private final FuzzedDataProvider target = new FuzzedDataProviderImpl();
+ private final ArrayList<Object> recordedReplies = new ArrayList<>();
+
+ private RecordingFuzzedDataProvider() {}
+
+ // Called from native code.
+ public static FuzzedDataProvider makeFuzzedDataProviderProxy() {
+ return (FuzzedDataProvider) Proxy.newProxyInstance(
+ RecordingFuzzedDataProvider.class.getClassLoader(), new Class[] {FuzzedDataProvider.class},
+ new RecordingFuzzedDataProvider());
+ }
+
+ // Called from native code.
+ public static String serializeFuzzedDataProviderProxy(FuzzedDataProvider proxy)
+ throws IOException {
+ return ((RecordingFuzzedDataProvider) Proxy.getInvocationHandler(proxy)).serialize();
+ }
+
+ private Object recordAndReturn(Object object) {
+ recordedReplies.add(object);
+ return object;
+ }
+
+ @Override
+ public Object invoke(Object object, Method method, Object[] args) throws Throwable {
+ if (method.isDefault()) {
+ // Default methods in FuzzedDataProvider are implemented in Java and
+ // don't need to be recorded.
+ return method.invoke(target, args);
+ } else {
+ return recordAndReturn(method.invoke(target, args));
+ }
+ }
+
+ private String serialize() throws IOException {
+ byte[] rawOut;
+ try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {
+ try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) {
+ objectStream.writeObject(recordedReplies);
+ }
+ rawOut = byteStream.toByteArray();
+ }
+ return Base64.getEncoder().encodeToString(rawOut);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java
new file mode 100644
index 00000000..0a42aa94
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/SignalHandler.java
@@ -0,0 +1,26 @@
+// Copyright 2021 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.runtime;
+
+import sun.misc.Signal;
+
+@SuppressWarnings({"unused", "sunapi"})
+final class SignalHandler {
+ public static native void handleInterrupt();
+
+ public static void setupSignalHandlers() {
+ Signal.handle(new Signal("INT"), sig -> handleInterrupt());
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java
new file mode 100644
index 00000000..352da8ea
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceCmpHooks.java
@@ -0,0 +1,330 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.TreeMap;
+
+@SuppressWarnings("unused")
+final public class TraceCmpHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte", targetMethod = "compare",
+ targetMethodDescriptor = "(BB)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(BB)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short", targetMethod = "compare",
+ targetMethodDescriptor = "(SS)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(SS)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compare", targetMethodDescriptor = "(II)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(II)I")
+ public static void
+ integerCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpInt((int) arguments[0], (int) arguments[1], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Byte",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Byte;)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Short",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Short;)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Integer;)I")
+ public static void
+ integerCompareTo(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpInt((int) thisObject, (int) arguments[0], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long", targetMethod = "compare",
+ targetMethodDescriptor = "(JJ)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "(JJ)I")
+ public static void
+ longCompare(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpLong((long) arguments[0], (long) arguments[1], hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "compareTo", targetMethodDescriptor = "(Ljava/lang/Long;)I")
+ public static void
+ longCompareTo(MethodHandle method, Long thisObject, Object[] arguments, int hookId) {
+ TraceDataFlowNativeCallbacks.traceCmpLong(thisObject, (long) arguments[0], hookId);
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "equalsIgnoreCase")
+ public static void
+ equals(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (arguments[0] instanceof String && !returnValue) {
+ // The precise value of the result of the comparison is not used by libFuzzer as long as it is
+ // non-zero.
+ TraceDataFlowNativeCallbacks.traceStrcmp(thisObject, (String) arguments[0], 1, hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "compareTo")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "compareToIgnoreCase")
+ public static void
+ compareTo(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (arguments[0] instanceof String && returnValue != 0) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ thisObject, (String) arguments[0], returnValue, hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contentEquals")
+ public static void
+ contentEquals(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (arguments[0] instanceof CharSequence && !returnValue) {
+ TraceDataFlowNativeCallbacks.traceStrcmp(
+ thisObject, ((CharSequence) arguments[0]).toString(), 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "regionMatches", targetMethodDescriptor = "(ZILjava/lang/String;II)Z")
+ public static void
+ regionsMatches5(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (!returnValue) {
+ int toffset = (int) arguments[1];
+ String other = (String) arguments[2];
+ int ooffset = (int) arguments[3];
+ int len = (int) arguments[4];
+ regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "regionMatches", targetMethodDescriptor = "(ILjava/lang/String;II)Z")
+ public static void
+ regionMatches4(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (!returnValue) {
+ int toffset = (int) arguments[0];
+ String other = (String) arguments[1];
+ int ooffset = (int) arguments[2];
+ int len = (int) arguments[3];
+ regionMatchesInternal((String) thisObject, toffset, other, ooffset, len, hookId);
+ }
+ }
+
+ private static void regionMatchesInternal(
+ String thisString, int toffset, String other, int ooffset, int len, int hookId) {
+ if (toffset < 0 || ooffset < 0)
+ return;
+ int cappedThisStringEnd = Math.min(toffset + len, thisString.length());
+ int cappedOtherStringEnd = Math.min(ooffset + len, other.length());
+ String thisPart = thisString.substring(toffset, cappedThisStringEnd);
+ String otherPart = other.substring(ooffset, cappedOtherStringEnd);
+ TraceDataFlowNativeCallbacks.traceStrcmp(thisPart, otherPart, 1, hookId);
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "contains")
+ public static void
+ contains(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (arguments[0] instanceof CharSequence && !returnValue) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ thisObject, ((CharSequence) arguments[0]).toString(), hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "indexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "lastIndexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuffer", targetMethod = "indexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuffer",
+ targetMethod = "lastIndexOf")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "indexOf")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "lastIndexOf")
+ public static void
+ indexOf(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ if (arguments[0] instanceof String && returnValue == -1) {
+ TraceDataFlowNativeCallbacks.traceStrstr(
+ thisObject.toString(), (String) arguments[0], hookId);
+ }
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "startsWith")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "endsWith")
+ public static void
+ startsWith(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ if (!returnValue) {
+ TraceDataFlowNativeCallbacks.traceStrstr(thisObject, (String) arguments[0], hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "replace",
+ targetMethodDescriptor =
+ "(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;")
+ public static void
+ replace(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) {
+ String original = (String) thisObject;
+ String target = arguments[0].toString();
+ // Report only if the replacement was not successful.
+ if (original.equals(returnValue)) {
+ TraceDataFlowNativeCallbacks.traceStrstr(original, target, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
+ targetMethodDescriptor = "([B[B)Z")
+ public static void
+ arraysEquals(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ byte[] first = (byte[]) arguments[0];
+ byte[] second = (byte[]) arguments[1];
+ if (!returnValue) {
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "equals",
+ targetMethodDescriptor = "([BII[BII)Z")
+ public static void
+ arraysEqualsRange(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Boolean returnValue) {
+ byte[] first =
+ Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
+ byte[] second =
+ Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
+ if (!returnValue) {
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, 1, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
+ targetMethodDescriptor = "([B[B)I")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "([B[B)I")
+ public static void
+ arraysCompare(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ byte[] first = (byte[]) arguments[0];
+ byte[] second = (byte[]) arguments[1];
+ if (returnValue != 0) {
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
+ }
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays", targetMethod = "compare",
+ targetMethodDescriptor = "([BII[BII)I")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Arrays",
+ targetMethod = "compareUnsigned", targetMethodDescriptor = "([BII[BII)I")
+ public static void
+ arraysCompareRange(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Integer returnValue) {
+ byte[] first =
+ Arrays.copyOfRange((byte[]) arguments[0], (int) arguments[1], (int) arguments[2]);
+ byte[] second =
+ Arrays.copyOfRange((byte[]) arguments[3], (int) arguments[4], (int) arguments[5]);
+ if (returnValue != 0) {
+ TraceDataFlowNativeCallbacks.traceMemcmp(first, second, returnValue, hookId);
+ }
+ }
+
+ // The maximal number of elements of a non-TreeMap Map that will be sorted and searched for the
+ // key closest to the current lookup key in the mapGet hook.
+ private static final int MAX_NUM_KEYS_TO_ENUMERATE = 100;
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "com.google.common.collect.ImmutableMap",
+ targetMethod = "get")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.util.AbstractMap", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.EnumMap", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.HashMap", targetMethod = "get")
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.util.LinkedHashMap", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.Map", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.SortedMap", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.TreeMap", targetMethod = "get")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.util.concurrent.ConcurrentMap",
+ targetMethod = "get")
+ public static void
+ mapGet(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ if (returnValue != null)
+ return;
+ if (thisObject == null)
+ return;
+ final Map map = (Map) thisObject;
+ if (map.size() == 0)
+ return;
+ final Object currentKey = arguments[0];
+ if (currentKey == null)
+ return;
+ // Find two valid map keys that bracket currentKey.
+ // This is a generalization of libFuzzer's __sanitizer_cov_trace_switch:
+ // https://github.com/llvm/llvm-project/blob/318942de229beb3b2587df09e776a50327b5cef0/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L564
+ Object lowerBoundKey = null;
+ Object upperBoundKey = null;
+ if (map instanceof TreeMap) {
+ final TreeMap treeMap = (TreeMap) map;
+ lowerBoundKey = treeMap.floorKey(currentKey);
+ upperBoundKey = treeMap.ceilingKey(currentKey);
+ } else if (currentKey instanceof Comparable) {
+ final Comparable comparableKey = (Comparable) currentKey;
+ // Find two keys that bracket currentKey.
+ // Note: This is not deterministic if map.size() > MAX_NUM_KEYS_TO_ENUMERATE.
+ int enumeratedKeys = 0;
+ for (Object validKey : map.keySet()) {
+ if (validKey == null)
+ continue;
+ // If the key sorts lower than the non-existing key, but higher than the current lower
+ // bound, update the lower bound and vice versa for the upper bound.
+ if (comparableKey.compareTo(validKey) > 0
+ && (lowerBoundKey == null || ((Comparable) validKey).compareTo(lowerBoundKey) > 0)) {
+ lowerBoundKey = validKey;
+ }
+ if (comparableKey.compareTo(validKey) < 0
+ && (upperBoundKey == null || ((Comparable) validKey).compareTo(upperBoundKey) < 0)) {
+ upperBoundKey = validKey;
+ }
+ if (enumeratedKeys++ > MAX_NUM_KEYS_TO_ENUMERATE)
+ break;
+ }
+ }
+ // Modify the hook ID so that compares against distinct valid keys are traced separately.
+ if (lowerBoundKey != null) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(
+ currentKey, lowerBoundKey, hookId + lowerBoundKey.hashCode());
+ }
+ if (upperBoundKey != null) {
+ TraceDataFlowNativeCallbacks.traceGenericCmp(
+ currentKey, upperBoundKey, hookId + upperBoundKey.hashCode());
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
new file mode 100644
index 00000000..456d0cb9
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java
@@ -0,0 +1,91 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.utils.Utils;
+import java.lang.reflect.Executable;
+
+@SuppressWarnings("unused")
+final public class TraceDataFlowNativeCallbacks {
+ /* trace-cmp */
+ // Calls: void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2);
+ public static native void traceCmpInt(int arg1, int arg2, int pc);
+
+ // Calls: void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2);
+ public static native void traceConstCmpInt(int arg1, int arg2, int pc);
+
+ // Calls: void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2);
+ public static native void traceCmpLong(long arg1, long arg2, int pc);
+
+ // Calls: void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases);
+ public static native void traceSwitch(long val, long[] cases, int pc);
+
+ // Calls: void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *b1, const void *b2,
+ // size_t n, int result);
+ public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc);
+
+ // Calls: void __sanitizer_weak_hook_strcmp(void *called_pc, const char *s1, const char *s2, int
+ // result);
+ public static native void traceStrcmp(String s1, String s2, int result, int pc);
+
+ // Calls: void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1, const char *s2, char
+ // *result);
+ public static native void traceStrstr(String s1, String s2, int pc);
+
+ /* trace-div */
+ // Calls: void __sanitizer_cov_trace_div4(uint32_t Val);
+ public static native void traceDivInt(int val, int pc);
+
+ // Calls: void __sanitizer_cov_trace_div8(uint64_t Val);
+ public static native void traceDivLong(long val, int pc);
+
+ /* trace-gep */
+ // Calls: void __sanitizer_cov_trace_gep(uintptr_t Idx);
+ public static native void traceGep(long val, int pc);
+
+ /* indirect-calls */
+ // Calls: void __sanitizer_cov_trace_pc_indir(uintptr_t Callee);
+ private static native void tracePcIndir(int callee, int caller);
+
+ public static void traceReflectiveCall(Executable callee, int pc) {
+ String className = callee.getDeclaringClass().getCanonicalName();
+ String executableName = callee.getName();
+ String descriptor = Utils.getDescriptor(callee);
+ tracePcIndir(Utils.simpleFastHash(className, executableName, descriptor), pc);
+ }
+
+ public static int traceCmpLongWrapper(long arg1, long arg2, int pc) {
+ traceCmpLong(arg1, arg2, pc);
+ // Long.compare serves as a substitute for the lcmp opcode, which can't be used directly
+ // as the stack layout required for the call can't be achieved without local variables.
+ return Long.compare(arg1, arg2);
+ }
+
+ // The caller has to ensure that arg1 and arg2 have the same class.
+ public static void traceGenericCmp(Object arg1, Object arg2, int pc) {
+ if (arg1 instanceof String) {
+ traceStrcmp((String) arg1, (String) arg2, 1, pc);
+ } else if (arg1 instanceof Integer || arg1 instanceof Short || arg1 instanceof Byte
+ || arg1 instanceof Character) {
+ traceCmpInt((int) arg1, (int) arg2, pc);
+ } else if (arg1 instanceof Long) {
+ traceCmpLong((long) arg1, (long) arg2, pc);
+ } else if (arg1 instanceof byte[]) {
+ traceMemcmp((byte[]) arg1, (byte[]) arg2, 1, pc);
+ }
+ }
+
+ public static native void handleLibraryLoad();
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java
new file mode 100644
index 00000000..c4991eb5
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceDivHooks.java
@@ -0,0 +1,47 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+@SuppressWarnings("unused")
+final public class TraceDivHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "divideUnsigned", targetMethodDescriptor = "(II)I")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Integer",
+ targetMethod = "remainderUnsigned", targetMethodDescriptor = "(II)I")
+ public static void
+ intUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ // Since the arguments are to be treated as unsigned integers we need a long to fit the
+ // divisor.
+ TraceDataFlowNativeCallbacks.traceDivLong(Integer.toUnsignedLong((int) arguments[1]), hookId);
+ }
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "divideUnsigned", targetMethodDescriptor = "(JJ)J")
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Long",
+ targetMethod = "remainderUnsigned", targetMethodDescriptor = "(JJ)J")
+ public static void
+ longUnsignedDivide(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ long divisor = (long) arguments[1];
+ // Run the callback only if the divisor, which is regarded as an unsigned long, fits in a
+ // signed long, i.e., does not have the sign bit set.
+ if (divisor > 0) {
+ TraceDataFlowNativeCallbacks.traceDivLong(divisor, hookId);
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java
new file mode 100644
index 00000000..897ede6c
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/runtime/TraceIndirHooks.java
@@ -0,0 +1,35 @@
+// Copyright 2021 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.runtime;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Executable;
+
+@SuppressWarnings("unused")
+final public class TraceIndirHooks {
+ // The reflection hook is of type AFTER as it should only report calls that did not fail because
+ // of incorrect arguments passed.
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.reflect.Method", targetMethod = "invoke")
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.reflect.Constructor",
+ targetMethod = "newInstance")
+ public static void
+ methodInvoke(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ TraceDataFlowNativeCallbacks.traceReflectiveCall((Executable) thisObject, hookId);
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel b/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel
new file mode 100644
index 00000000..5e301efc
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/BUILD.bazel
@@ -0,0 +1,10 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+kt_jvm_library(
+ name = "utils",
+ srcs = [
+ "ClassNameGlobber.kt",
+ "Utils.kt",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt
new file mode 100644
index 00000000..1f09afe3
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/ClassNameGlobber.kt
@@ -0,0 +1,102 @@
+// Copyright 2021 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.utils
+
+import java.lang.IllegalArgumentException
+
+private val BASE_INCLUDED_CLASS_NAME_GLOBS = listOf(
+ "**", // everything
+)
+
+private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf(
+ "\\[**", // array types
+ "com.code_intelligence.jazzer.**",
+ "com.sun.**", // package for Proxy objects
+ "java.**",
+ "javax.**",
+ "jaz.Ter", // safe companion of the honeypot class used by sanitizers
+ "jaz.Zer", // honeypot class used by sanitizers
+ "jdk.**",
+ "kotlin.**",
+ "sun.**",
+)
+
+class ClassNameGlobber(includes: List<String>, excludes: List<String>) {
+ // If no include globs are provided, start with all classes.
+ private val includeMatchers = (if (includes.isEmpty()) BASE_INCLUDED_CLASS_NAME_GLOBS else includes)
+ .map(::SimpleGlobMatcher)
+
+ // If no include globs are provided, additionally exclude stdlib classes as well as our own classes.
+ private val excludeMatchers = (if (includes.isEmpty()) BASE_EXCLUDED_CLASS_NAME_GLOBS + excludes else excludes)
+ .map(::SimpleGlobMatcher)
+
+ fun includes(className: String): Boolean {
+ return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) }
+ }
+}
+
+class SimpleGlobMatcher(val glob: String) {
+ private enum class Type {
+ // foo.bar (matches foo.bar only)
+ FULL_MATCH,
+ // foo.** (matches foo.bar and foo.bar.baz)
+ PATH_WILDCARD_SUFFIX,
+ // foo.* (matches foo.bar, but not foo.bar.baz)
+ SEGMENT_WILDCARD_SUFFIX,
+ }
+
+ private val type: Type
+ private val prefix: String
+
+ init {
+ // Remain compatible with globs such as "\\[" that use escaping.
+ val pattern = glob.replace("\\", "")
+ when {
+ !pattern.contains('*') -> {
+ type = Type.FULL_MATCH
+ prefix = pattern
+ }
+ // Ends with "**" and contains no other '*'.
+ pattern.endsWith("**") && pattern.indexOf('*') == pattern.length - 2 -> {
+ type = Type.PATH_WILDCARD_SUFFIX
+ prefix = pattern.removeSuffix("**")
+ }
+ // Ends with "*" and contains no other '*'.
+ pattern.endsWith('*') && pattern.indexOf('*') == pattern.length - 1 -> {
+ type = Type.SEGMENT_WILDCARD_SUFFIX
+ prefix = pattern.removeSuffix("*")
+ }
+ else -> throw IllegalArgumentException(
+ "Unsupported glob pattern (only foo.bar, foo.* and foo.** are supported): $pattern"
+ )
+ }
+ }
+
+ /**
+ * Checks whether [maybeInternalClassName], which may be internal (foo/bar) or not (foo.bar), matches [glob].
+ */
+ fun matches(maybeInternalClassName: String): Boolean {
+ val className = maybeInternalClassName.replace('/', '.')
+ return when (type) {
+ Type.FULL_MATCH -> className == prefix
+ Type.PATH_WILDCARD_SUFFIX -> className.startsWith(prefix)
+ Type.SEGMENT_WILDCARD_SUFFIX -> {
+ // className starts with prefix and contains no further '.'.
+ className.startsWith(prefix) &&
+ className.indexOf('.', startIndex = prefix.length) == -1
+ }
+ }
+ }
+}
diff --git a/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt b/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt
new file mode 100644
index 00000000..af8cce9b
--- /dev/null
+++ b/agent/src/main/java/com/code_intelligence/jazzer/utils/Utils.kt
@@ -0,0 +1,82 @@
+// Copyright 2021 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.
+@file:JvmName("Utils")
+
+package com.code_intelligence.jazzer.utils
+
+import java.lang.reflect.Executable
+import java.lang.reflect.Method
+
+val Class<*>.descriptor: String
+ get() = when {
+ isPrimitive -> {
+ when (this) {
+ Boolean::class.javaPrimitiveType -> "Z"
+ Byte::class.javaPrimitiveType -> "B"
+ Char::class.javaPrimitiveType -> "C"
+ Short::class.javaPrimitiveType -> "S"
+ Int::class.javaPrimitiveType -> "I"
+ Long::class.javaPrimitiveType -> "J"
+ Float::class.javaPrimitiveType -> "F"
+ Double::class.javaPrimitiveType -> "D"
+ java.lang.Void::class.javaPrimitiveType -> "V"
+ else -> throw IllegalStateException("Unknown primitive type: $name")
+ }
+ }
+ isArray -> "[${componentType.descriptor}"
+ java.lang.Object::class.java.isAssignableFrom(this) -> "L${name.replace('.', '/')};"
+ else -> throw IllegalArgumentException("Unknown class type: $name")
+ }
+
+val Class<*>.readableDescriptor: String
+ get() = when {
+ isPrimitive -> {
+ when (this) {
+ Boolean::class.javaPrimitiveType -> "boolean"
+ Byte::class.javaPrimitiveType -> "byte"
+ Char::class.javaPrimitiveType -> "char"
+ Short::class.javaPrimitiveType -> "short"
+ Int::class.javaPrimitiveType -> "int"
+ Long::class.javaPrimitiveType -> "long"
+ Float::class.javaPrimitiveType -> "float"
+ Double::class.javaPrimitiveType -> "double"
+ java.lang.Void::class.javaPrimitiveType -> "void"
+ else -> throw IllegalStateException("Unknown primitive type: $name")
+ }
+ }
+ isArray -> "${componentType.readableDescriptor}[]"
+ java.lang.Object::class.java.isAssignableFrom(this) -> name
+ else -> throw IllegalArgumentException("Unknown class type: $name")
+ }
+
+val Executable.descriptor: String
+ get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")") { parameterType ->
+ parameterType.descriptor
+ } + if (this is Method) returnType.descriptor else "V"
+
+// This does not include the return type as the parameter descriptors already uniquely identify the executable.
+val Executable.readableDescriptor: String
+ get() = parameterTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { parameterType ->
+ parameterType.readableDescriptor
+ }
+
+fun simpleFastHash(vararg strings: String): Int {
+ var hash = 0
+ for (string in strings) {
+ for (c in string) {
+ hash = hash * 11 + c.code
+ }
+ }
+ return hash
+}
diff --git a/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel b/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel
new file mode 100644
index 00000000..6b75fb8b
--- /dev/null
+++ b/agent/src/main/native/com/code_intelligence/jazzer/replay/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library")
+
+cc_jni_library(
+ name = "replay",
+ srcs = [
+ "com_code_intelligence_jazzer_replay_Replayer.cpp",
+ ],
+ visibility = ["//agent/src/main/java/com/code_intelligence/jazzer/replay:__pkg__"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/replay:replay.hdrs",
+ "//driver:fuzzed_data_provider",
+ ],
+)
diff --git a/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp b/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp
new file mode 100644
index 00000000..c4bdfcfb
--- /dev/null
+++ b/agent/src/main/native/com/code_intelligence/jazzer/replay/com_code_intelligence_jazzer_replay_Replayer.cpp
@@ -0,0 +1,48 @@
+// Copyright 2021 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.
+
+#include "com_code_intelligence_jazzer_replay_Replayer.h"
+
+#include <jni.h>
+
+#include "driver/fuzzed_data_provider.h"
+
+namespace {
+uint8_t *data = nullptr;
+}
+
+void Java_com_code_1intelligence_jazzer_replay_Replayer_feedFuzzedDataProvider(
+ JNIEnv *env, jclass, jbyteArray input) {
+ if (data == nullptr) {
+ jazzer::SetUpFuzzedDataProvider(*env);
+ } else {
+ delete[] data;
+ }
+
+ std::size_t size = env->GetArrayLength(input);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->FatalError("Failed to get length of input");
+ }
+ data = static_cast<uint8_t *>(operator new(size));
+ if (data == nullptr) {
+ env->FatalError("Failed to allocate memory for a copy of the input");
+ }
+ env->GetByteArrayRegion(input, 0, size, reinterpret_cast<jbyte *>(data));
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->FatalError("Failed to copy input");
+ }
+ jazzer::FeedFuzzedDataProvider(data, size);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java
new file mode 100644
index 00000000..66a85db6
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/api/AutofuzzTest.java
@@ -0,0 +1,107 @@
+// Copyright 2021 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.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+
+public class AutofuzzTest {
+ public interface UnimplementedInterface {}
+
+ public interface ImplementedInterface {}
+ public static class ImplementingClass implements ImplementedInterface {}
+
+ private static boolean implIsNotNull(ImplementedInterface impl) {
+ return impl != null;
+ }
+
+ private static boolean implIsNotNull(UnimplementedInterface impl) {
+ return impl != null;
+ }
+
+ private static void checkAllTheArguments(
+ String arg1, int arg2, byte arg3, ImplementedInterface arg4) {
+ if (!arg1.equals("foobar") || arg2 != 42 || arg3 != 5 || arg4 == null) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Test
+ public void testConsume() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ ImplementedInterface result = Jazzer.consume(data, ImplementedInterface.class);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testConsumeFailsWithoutException() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(Collections.singletonList(
+ (byte) 1 /* do not return null without searching for implementing classes */));
+ assertNull(Jazzer.consume(data, UnimplementedInterface.class));
+ }
+
+ @Test
+ public void testAutofuzz() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ assertEquals(Boolean.TRUE,
+ Jazzer.autofuzz(data, (Function1<ImplementedInterface, ?>) AutofuzzTest::implIsNotNull));
+ }
+
+ @Test
+ public void testAutofuzzFailsWithException() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Collections.singletonList((byte) 1 /* do not return null */));
+ try {
+ Jazzer.autofuzz(data, (Function1<UnimplementedInterface, ?>) AutofuzzTest::implIsNotNull);
+ } catch (AutofuzzConstructionException e) {
+ // Pass.
+ return;
+ }
+ fail("should have thrown an AutofuzzConstructionException");
+ }
+
+ @Test
+ public void testAutofuzzConsumer() {
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(
+ Arrays.asList((byte) 1 /* do not return null */, 6 /* string length */, "foobar", 42,
+ (byte) 5, (byte) 1 /* do not return null */, 0 /* first class on the classpath */,
+ (byte) 1 /* do not return null */, 0 /* first constructor */));
+ Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments);
+ }
+
+ @Test
+ public void testAutofuzzConsumerThrowsException() {
+ FuzzedDataProvider data =
+ CannedFuzzedDataProvider.create(Arrays.asList((byte) 1 /* do not return null */,
+ 6 /* string length */, "foobar", 42, (byte) 5, (byte) 0 /* *do* return null */));
+ try {
+ Jazzer.autofuzz(data, AutofuzzTest::checkAllTheArguments);
+ } catch (IllegalArgumentException e) {
+ // Pass.
+ return;
+ }
+ fail("should have thrown an IllegalArgumentException");
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel
new file mode 100644
index 00000000..9192ff77
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/api/BUILD.bazel
@@ -0,0 +1,21 @@
+java_test(
+ name = "AutofuzzTest",
+ size = "small",
+ srcs = [
+ "AutofuzzTest.java",
+ ],
+ env = {
+ # Also consider implementing classes from com.code_intelligence.jazzer.*.
+ "JAZZER_AUTOFUZZ_TESTING": "1",
+ },
+ test_class = "com.code_intelligence.jazzer.api.AutofuzzTest",
+ runtime_deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz",
+ # Needed for JazzerInternal.
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime",
+ ],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@maven//:junit_junit",
+ ],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
new file mode 100644
index 00000000..f8448f01
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BUILD.bazel
@@ -0,0 +1,69 @@
+java_test(
+ name = "MetaTest",
+ size = "small",
+ srcs = [
+ "MetaTest.java",
+ ],
+ test_class = "com.code_intelligence.jazzer.autofuzz.MetaTest",
+ deps = [
+ ":test_helpers",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz",
+ "@maven//:com_mikesamuel_json_sanitizer",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "InterfaceCreationTest",
+ size = "small",
+ srcs = [
+ "InterfaceCreationTest.java",
+ ],
+ env = {
+ # Also consider implementing classes from com.code_intelligence.jazzer.*.
+ "JAZZER_AUTOFUZZ_TESTING": "1",
+ },
+ test_class = "com.code_intelligence.jazzer.autofuzz.InterfaceCreationTest",
+ deps = [
+ ":test_helpers",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "BuilderPatternTest",
+ size = "small",
+ srcs = [
+ "BuilderPatternTest.java",
+ ],
+ test_class = "com.code_intelligence.jazzer.autofuzz.BuilderPatternTest",
+ deps = [
+ ":test_helpers",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_test(
+ name = "SettersTest",
+ size = "small",
+ srcs = [
+ "SettersTest.java",
+ ],
+ test_class = "com.code_intelligence.jazzer.autofuzz.SettersTest",
+ deps = [
+ ":test_helpers",
+ "//agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata:test_data",
+ "@maven//:junit_junit",
+ ],
+)
+
+java_library(
+ name = "test_helpers",
+ srcs = ["TestHelpers.java"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/autofuzz",
+ "@maven//:junit_junit",
+ ],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java
new file mode 100644
index 00000000..a602d712
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/BuilderPatternTest.java
@@ -0,0 +1,103 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+import java.util.Arrays;
+import java.util.Objects;
+import org.junit.Test;
+
+class Employee {
+ private final String firstName;
+ private final String lastName;
+ private final String jobTitle;
+ private final int age;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Employee hero = (Employee) o;
+ return age == hero.age && Objects.equals(firstName, hero.firstName)
+ && Objects.equals(lastName, hero.lastName) && Objects.equals(jobTitle, hero.jobTitle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName, jobTitle, age);
+ }
+
+ private Employee(Builder builder) {
+ this.jobTitle = builder.jobTitle;
+ this.firstName = builder.firstName;
+ this.lastName = builder.lastName;
+ this.age = builder.age;
+ }
+
+ public static class Builder {
+ private final String firstName;
+ private final String lastName;
+ private String jobTitle;
+ private int age;
+
+ public Builder(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public Builder withAge(int age) {
+ this.age = age;
+ return this;
+ }
+
+ public Builder withJobTitle(String jobTitle) {
+ this.jobTitle = jobTitle;
+ return this;
+ }
+
+ public Employee build() {
+ return new Employee(this);
+ }
+ }
+}
+
+public class BuilderPatternTest {
+ @Test
+ public void testBuilderPattern() {
+ consumeTestCase(new Employee.Builder("foo", "bar").withAge(20).withJobTitle("baz").build(),
+ "new com.code_intelligence.jazzer.autofuzz.Employee.Builder(\"foo\", \"bar\").withAge(20).withJobTitle(\"baz\").build()",
+ Arrays.asList((byte) 1, // do not return null
+ 0, // Select the first Builder
+ 2, // Select two Builder methods returning a builder object (fluent design)
+ 0, // Select the first build method
+ 0, // pick the first remaining builder method (withAge)
+ 0, // pick the first remaining builder method (withJobTitle)
+ 0, // pick the first build method
+ (byte) 1, // do not return null
+ 6, // remaining bytes
+ "foo", // firstName
+ (byte) 1, // do not return null
+ 6, // remaining bytes
+ "bar", // lastName
+ 20, // age
+ (byte) 1, // do not return null
+ 6, // remaining bytes
+ "baz" // jobTitle
+ ));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java
new file mode 100644
index 00000000..4d85ca6c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/InterfaceCreationTest.java
@@ -0,0 +1,111 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+import java.util.Arrays;
+import java.util.Objects;
+import org.junit.Test;
+
+interface InterfaceA {
+ void foo();
+
+ void bar();
+}
+
+abstract class ClassA1 implements InterfaceA {
+ @Override
+ public void foo() {}
+}
+
+class ClassB1 extends ClassA1 {
+ int n;
+
+ public ClassB1(int _n) {
+ n = _n;
+ }
+
+ @Override
+ public void bar() {}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ClassB1 classB1 = (ClassB1) o;
+ return n == classB1.n;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(n);
+ }
+}
+
+class ClassB2 implements InterfaceA {
+ String s;
+
+ public ClassB2(String _s) {
+ s = _s;
+ }
+
+ @Override
+ public void foo() {}
+
+ @Override
+ public void bar() {}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ClassB2 classB2 = (ClassB2) o;
+ return Objects.equals(s, classB2.s);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(s);
+ }
+}
+
+public class InterfaceCreationTest {
+ @Test
+ public void testConsumeInterface() {
+ consumeTestCase(InterfaceA.class, new ClassB1(5),
+ "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB1(5)",
+ Arrays.asList((byte) 1, // do not return null
+ 0, // pick ClassB1
+ (byte) 1, // do not return null
+ 0, // pick first constructor
+ 5 // arg for ClassB1 constructor
+ ));
+ consumeTestCase(InterfaceA.class, new ClassB2("test"),
+ "(com.code_intelligence.jazzer.autofuzz.InterfaceA) new com.code_intelligence.jazzer.autofuzz.ClassB2(\"test\")",
+ Arrays.asList((byte) 1, // do not return null
+ 1, // pick ClassB2
+ (byte) 1, // do not return null
+ 0, // pick first constructor
+ (byte) 1, // do not return null
+ 8, // remaining bytes
+ "test" // arg for ClassB2 constructor
+ ));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java
new file mode 100644
index 00000000..0615e9ae
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/MetaTest.java
@@ -0,0 +1,147 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.autofuzzTestCase;
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+import static org.junit.Assert.assertEquals;
+
+import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import com.google.json.JsonSanitizer;
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+
+public class MetaTest {
+ public static boolean isFive(int arg) {
+ return arg == 5;
+ }
+
+ public static boolean intEquals(int arg1, int arg2) {
+ return arg1 == arg2;
+ }
+
+ public enum TestEnum {
+ FOO,
+ BAR,
+ BAZ,
+ }
+
+ @Test
+ public void testConsume() {
+ consumeTestCase(5, "5", Collections.singletonList(5));
+ consumeTestCase((short) 5, "(short) 5", Collections.singletonList((short) 5));
+ consumeTestCase(5L, "5L", Collections.singletonList(5L));
+ consumeTestCase(5.0F, "5.0F", Collections.singletonList(5.0F));
+ consumeTestCase('\n', "'\\\\n'", Collections.singletonList('\n'));
+ consumeTestCase('\'', "'\\\\''", Collections.singletonList('\''));
+ consumeTestCase('\\', "'\\\\'", Collections.singletonList('\\'));
+
+ String testString = "foo\n\t\\\"bar";
+ // The expected string is obtained from testString by escaping, wrapping into quotes and
+ // escaping again.
+ consumeTestCase(testString, "\"foo\\\\n\\\\t\\\\\\\\\"bar\"",
+ Arrays.asList((byte) 1, // do not return null
+ testString.length(), testString));
+
+ consumeTestCase(null, "null", Collections.singletonList((byte) 0));
+
+ boolean[] testBooleans = new boolean[] {true, false, true};
+ consumeTestCase(testBooleans, "new boolean[]{true, false, true}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 2 * 3, testBooleans));
+
+ char[] testChars = new char[] {'a', '\n', '\''};
+ consumeTestCase(testChars, "new char[]{'a', '\\\\n', '\\\\''}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 2 * 3 * Character.BYTES + Character.BYTES, testChars[0], 2 * 3 * Character.BYTES,
+ 2 * 3 * Character.BYTES, // remaining bytes, 2 times what is needed for 3 chars
+ testChars[1], testChars[2]));
+
+ char[] testNoChars = new char[] {};
+ consumeTestCase(testNoChars, "new char[]{}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 0, 'a', 0, 0));
+
+ short[] testShorts = new short[] {(short) 1, (short) 2, (short) 3};
+ consumeTestCase(testShorts, "new short[]{(short) 1, (short) 2, (short) 3}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 2 * 3 * Short.BYTES, // remaining bytes
+ testShorts));
+
+ long[] testLongs = new long[] {1L, 2L, 3L};
+ consumeTestCase(testLongs, "new long[]{1L, 2L, 3L}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 2 * 3 * Long.BYTES, // remaining bytes
+ testLongs));
+
+ consumeTestCase(new String[] {"foo", "bar", "foo\nbar"},
+ "new java.lang.String[]{\"foo\", \"bar\", \"foo\\\\nbar\"}",
+ Arrays.asList((byte) 1, // do not return null for the array
+ 32, // remaining bytes
+ (byte) 1, // do not return null for the string
+ 31, // remaining bytes
+ "foo",
+ 28, // remaining bytes
+ 28, // array length
+ (byte) 1, // do not return null for the string
+ 27, // remaining bytes
+ "bar",
+ (byte) 1, // do not return null for the string
+ 23, // remaining bytes
+ "foo\nbar"));
+
+ byte[] testInputStreamBytes = new byte[] {(byte) 1, (byte) 2, (byte) 3};
+ consumeTestCase(new ByteArrayInputStream(testInputStreamBytes),
+ "new java.io.ByteArrayInputStream(new byte[]{(byte) 1, (byte) 2, (byte) 3})",
+ Arrays.asList((byte) 1, // do not return null for the InputStream
+ 2 * 3, // remaining bytes (twice the desired length)
+ testInputStreamBytes));
+
+ consumeTestCase(TestEnum.BAR,
+ String.format("%s.%s", TestEnum.class.getName(), TestEnum.BAR.name()),
+ Arrays.asList((byte) 1, // do not return null for the enum value
+ 1 /* second value */
+ ));
+
+ consumeTestCase(YourAverageJavaClass.class,
+ "com.code_intelligence.jazzer.autofuzz.YourAverageJavaClass.class",
+ Collections.singletonList((byte) 1));
+ }
+
+ @Test
+ public void testAutofuzz() throws NoSuchMethodException {
+ autofuzzTestCase(true, "com.code_intelligence.jazzer.autofuzz.MetaTest.isFive(5)",
+ MetaTest.class.getMethod("isFive", int.class), Collections.singletonList(5));
+ autofuzzTestCase(false, "com.code_intelligence.jazzer.autofuzz.MetaTest.intEquals(5, 4)",
+ MetaTest.class.getMethod("intEquals", int.class, int.class), Arrays.asList(5, 4));
+ autofuzzTestCase("foobar", "\"foo\".concat(\"bar\")",
+ String.class.getMethod("concat", String.class),
+ Arrays.asList((byte) 1, 6, "foo", (byte) 1, 6, "bar"));
+ autofuzzTestCase("jazzer", "new java.lang.String(\"jazzer\")",
+ String.class.getConstructor(String.class), Arrays.asList((byte) 1, 12, "jazzer"));
+ autofuzzTestCase("\"jazzer\"", "com.google.json.JsonSanitizer.sanitize(\"jazzer\")",
+ JsonSanitizer.class.getMethod("sanitize", String.class),
+ Arrays.asList((byte) 1, 12, "jazzer"));
+
+ FuzzedDataProvider data =
+ CannedFuzzedDataProvider.create(Arrays.asList((byte) 1, // do not return null
+ 8, // remainingBytes
+ "buzz"));
+ assertEquals("fizzbuzz", Meta.autofuzz(data, "fizz" ::concat));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java
new file mode 100644
index 00000000..7c869531
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/SettersTest.java
@@ -0,0 +1,43 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import static com.code_intelligence.jazzer.autofuzz.TestHelpers.consumeTestCase;
+
+import com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class SettersTest {
+ @Test
+ public void testEmptyConstructorWithSetters() {
+ EmployeeWithSetters employee = new EmployeeWithSetters();
+ employee.setFirstName("foo");
+ employee.setAge(26);
+
+ consumeTestCase(employee,
+ "((java.util.function.Supplier<com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters>) (() -> {com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters autofuzzVariable0 = new com.code_intelligence.jazzer.autofuzz.testdata.EmployeeWithSetters(); autofuzzVariable0.setFirstName(\"foo\"); autofuzzVariable0.setAge(26); return autofuzzVariable0;})).get()",
+ Arrays.asList((byte) 1, // do not return null for EmployeeWithSetters
+ 0, // pick first constructor
+ 2, // pick two setters
+ 1, // pick second setter
+ 0, // pick first setter
+ (byte) 1, // do not return null for String
+ 6, // remaining bytes
+ "foo", // setFirstName
+ 26 // setAge
+ ));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java
new file mode 100644
index 00000000..52f19a74
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/TestHelpers.java
@@ -0,0 +1,85 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.code_intelligence.jazzer.api.CannedFuzzedDataProvider;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.io.ByteArrayInputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.List;
+
+class TestHelpers {
+ static void assertGeneralEquals(Object expected, Object actual) {
+ Class<?> type = expected != null ? expected.getClass() : Object.class;
+ if (type.isArray()) {
+ if (type.getComponentType() == boolean.class) {
+ assertArrayEquals((boolean[]) expected, (boolean[]) actual);
+ } else if (type.getComponentType() == char.class) {
+ assertArrayEquals((char[]) expected, (char[]) actual);
+ } else if (type.getComponentType() == short.class) {
+ assertArrayEquals((short[]) expected, (short[]) actual);
+ } else if (type.getComponentType() == long.class) {
+ assertArrayEquals((long[]) expected, (long[]) actual);
+ } else {
+ assertArrayEquals((Object[]) expected, (Object[]) actual);
+ }
+ } else if (type == ByteArrayInputStream.class) {
+ ByteArrayInputStream expectedStream = (ByteArrayInputStream) expected;
+ ByteArrayInputStream actualStream = (ByteArrayInputStream) actual;
+ assertArrayEquals(readAllBytes(expectedStream), readAllBytes(actualStream));
+ } else {
+ assertEquals(expected, actual);
+ }
+ }
+
+ static void consumeTestCase(
+ Object expectedResult, String expectedResultString, List<Object> cannedData) {
+ Class<?> type = expectedResult != null ? expectedResult.getClass() : Object.class;
+ consumeTestCase(type, expectedResult, expectedResultString, cannedData);
+ }
+
+ static void consumeTestCase(
+ Class<?> type, Object expectedResult, String expectedResultString, List<Object> cannedData) {
+ assertTrue(expectedResult == null || type.isAssignableFrom(expectedResult.getClass()));
+ AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor();
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData);
+ assertGeneralEquals(expectedResult, Meta.consume(data, type, visitor));
+ assertEquals(expectedResultString, visitor.generate());
+ }
+
+ static void autofuzzTestCase(Object expectedResult, String expectedResultString, Executable func,
+ List<Object> cannedData) {
+ AutofuzzCodegenVisitor visitor = new AutofuzzCodegenVisitor();
+ FuzzedDataProvider data = CannedFuzzedDataProvider.create(cannedData);
+ if (func instanceof Method) {
+ assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Method) func, visitor));
+ } else {
+ assertGeneralEquals(expectedResult, Meta.autofuzz(data, (Constructor<?>) func, visitor));
+ }
+ assertEquals(expectedResultString, visitor.generate());
+ }
+
+ private static byte[] readAllBytes(ByteArrayInputStream in) {
+ byte[] result = new byte[in.available()];
+ in.read(result, 0, in.available());
+ return result;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel
new file mode 100644
index 00000000..c2c68803
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/BUILD.bazel
@@ -0,0 +1,5 @@
+java_library(
+ name = "test_data",
+ srcs = glob(["*.java"]),
+ visibility = ["//visibility:public"],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java
new file mode 100644
index 00000000..2c76a61f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/autofuzz/testdata/EmployeeWithSetters.java
@@ -0,0 +1,56 @@
+// Copyright 2021 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.code_intelligence.jazzer.autofuzz.testdata;
+
+import java.util.Objects;
+
+public class EmployeeWithSetters {
+ private String firstName;
+ private String lastName;
+ private String jobTitle;
+ private int age;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ EmployeeWithSetters hero = (EmployeeWithSetters) o;
+ return age == hero.age && Objects.equals(firstName, hero.firstName)
+ && Objects.equals(lastName, hero.lastName) && Objects.equals(jobTitle, hero.jobTitle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(firstName, lastName, jobTitle, age);
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public void setJobTitle(String jobTitle) {
+ this.jobTitle = jobTitle;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
new file mode 100644
index 00000000..f8d6782c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooks.java
@@ -0,0 +1,87 @@
+// Copyright 2021 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class AfterHooks {
+ static AfterHooksTargetContract instance;
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "func1")
+ public static void
+ patchFunc1(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ instance = (AfterHooksTargetContract) thisObject;
+ ((AfterHooksTargetContract) thisObject).registerHasFunc1BeenCalled();
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "registerTimesCalled", targetMethodDescriptor = "()V")
+ public static void
+ patchRegisterTimesCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId,
+ Object returnValue) throws Throwable {
+ // Invoke registerTimesCalled() again to pass the test.
+ method.invoke();
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "getFirstSecret", targetMethodDescriptor = "()Ljava/lang/String;")
+ public static void
+ patchGetFirstSecret(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, String returnValue) {
+ // Use the returned secret to pass the test.
+ ((AfterHooksTargetContract) thisObject).verifyFirstSecret(returnValue);
+ }
+
+ @MethodHook(type = HookType.AFTER,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.AfterHooksTarget",
+ targetMethod = "getSecondSecret")
+ public static void
+ patchGetSecondSecret(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ // Use the returned secret to pass the test.
+ ((AfterHooksTargetContract) thisObject).verifySecondSecret((String) returnValue);
+ }
+
+ // Verify the interaction of a BEFORE and an AFTER hook. The BEFORE hook modifies the argument of
+ // the StringBuilder constructor.
+ @MethodHook(
+ type = HookType.BEFORE, targetClassName = "java.lang.StringBuilder", targetMethod = "<init>")
+ public static void
+ patchStringBuilderBeforeInit(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ arguments[0] = "hunter3";
+ }
+
+ @MethodHook(
+ type = HookType.AFTER, targetClassName = "java.lang.StringBuilder", targetMethod = "<init>")
+ public static void
+ patchStringBuilderInit(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
+ String secret = ((StringBuilder) thisObject).toString();
+ // Verify that the argument passed to this AFTER hook agrees with the argument passed to the
+ // StringBuilder constructor, which has been modified by the BEFORE hook.
+ if (secret.equals(arguments[0])) {
+ // Verify that the argument has been modified to the correct value "hunter3".
+ instance.verifyThirdSecret(secret);
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
new file mode 100644
index 00000000..53efd200
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// Copyright 2021 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyAfterHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(AfterHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalAfterHooksTargetInstance(): AfterHooksTargetContract {
+ return AfterHooksTarget()
+}
+
+private fun getNoHooksAfterHooksTargetInstance(): AfterHooksTargetContract {
+ val originalBytecode = classToBytecode(AfterHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract
+}
+
+private fun getPatchedAfterHooksTargetInstance(): AfterHooksTargetContract {
+ val originalBytecode = classToBytecode(AfterHooksTarget::class.java)
+ val patchedBytecode = applyAfterHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${AfterHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${AfterHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(AfterHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as AfterHooksTargetContract
+}
+
+class AfterHookTest {
+
+ @Test
+ fun testAfterHooksOriginal() {
+ assertSelfCheck(getOriginalAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testAfterHooksNoHooks() {
+ assertSelfCheck(getNoHooksAfterHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testAfterHooksPatched() {
+ assertSelfCheck(getPatchedAfterHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java
new file mode 100644
index 00000000..a47b03a5
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTarget.java
@@ -0,0 +1,85 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in AfterHooks.java applied.
+public class AfterHooksTarget implements AfterHooksTargetContract {
+ static Map<String, Boolean> results = new HashMap<>();
+ static int timesCalled = 0;
+ Boolean func1Called = false;
+
+ public static void registerTimesCalled() {
+ timesCalled++;
+ results.put("hasBeenCalledTwice", timesCalled == 2);
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ if (results.isEmpty()) {
+ registerHasFunc1BeenCalled();
+ func1();
+ }
+
+ timesCalled = 0;
+ registerTimesCalled();
+
+ verifyFirstSecret("not_secret");
+ getFirstSecret();
+
+ verifySecondSecret("not_secret_at_all");
+ getSecondSecret();
+
+ verifyThirdSecret("not_the_secret");
+ new StringBuilder("not_hunter3");
+
+ return results;
+ }
+
+ public void func1() {
+ func1Called = true;
+ }
+
+ public void registerHasFunc1BeenCalled() {
+ results.put("hasFunc1BeenCalled", func1Called);
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ String getFirstSecret() {
+ return "hunter2";
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public void verifyFirstSecret(String secret) {
+ results.put("verifyFirstSecret", secret.equals("hunter2"));
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ String getSecondSecret() {
+ return "hunter2!";
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ public void verifySecondSecret(String secret) {
+ results.put("verifySecondSecret", secret.equals("hunter2!"));
+ }
+
+ public void verifyThirdSecret(String secret) {
+ results.put("verifyThirdSecret", secret.equals("hunter3"));
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
new file mode 100644
index 00000000..cb12b148
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/AfterHooksTargetContract.java
@@ -0,0 +1,29 @@
+// Copyright 2021 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of AfterHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface AfterHooksTargetContract extends DynamicTestContract {
+ void registerHasFunc1BeenCalled();
+
+ void verifyFirstSecret(String secret);
+
+ void verifySecondSecret(String secret);
+
+ void verifyThirdSecret(String secret);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
new file mode 100644
index 00000000..472d2b98
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel
@@ -0,0 +1,147 @@
+load("//bazel:kotlin.bzl", "wrapped_kt_jvm_test")
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+kt_jvm_library(
+ name = "patch_test_utils",
+ srcs = [
+ "DynamicTestContract.java",
+ "PatchTestUtils.kt",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "trace_data_flow_instrumentation_test",
+ size = "small",
+ srcs = [
+ "MockTraceDataFlowCallbacks.java",
+ "TraceDataFlowInstrumentationTarget.java",
+ "TraceDataFlowInstrumentationTest.kt",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.TraceDataFlowInstrumentationTest",
+ deps = [
+ ":patch_test_utils",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "coverage_instrumentation_test",
+ size = "small",
+ srcs = [
+ "CoverageInstrumentationSpecialCasesTarget.java",
+ "CoverageInstrumentationTarget.java",
+ "CoverageInstrumentationTest.kt",
+ "MockCoverageMap.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.CoverageInstrumentationTest",
+ deps = [
+ ":patch_test_utils",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "descriptor_utils_test",
+ size = "small",
+ srcs = [
+ "DescriptorUtilsTest.kt",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.DescriptorUtilsTest",
+ deps = [
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "hook_validation_test",
+ size = "small",
+ srcs = [
+ "HookValidationTest.kt",
+ "InvalidHookMocks.java",
+ "ValidHookMocks.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.HookValidationTest",
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "after_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "AfterHooks.java",
+ "AfterHooksPatchTest.kt",
+ "AfterHooksTarget.java",
+ "AfterHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.AfterHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "before_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "BeforeHooks.java",
+ "BeforeHooksPatchTest.kt",
+ "BeforeHooksTarget.java",
+ "BeforeHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.BeforeHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
+
+wrapped_kt_jvm_test(
+ name = "replace_hooks_patch_test",
+ size = "small",
+ srcs = [
+ "ReplaceHooks.java",
+ "ReplaceHooksPatchTest.kt",
+ "ReplaceHooksTarget.java",
+ "ReplaceHooksTargetContract.java",
+ ],
+ associates = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/instrumentor:instrumentor",
+ ],
+ test_class = "com.code_intelligence.jazzer.instrumentor.ReplaceHookTest",
+ deps = [
+ ":patch_test_utils",
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@maven//:junit_junit",
+ ],
+)
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java
new file mode 100644
index 00000000..31577dad
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooks.java
@@ -0,0 +1,53 @@
+// Copyright 2021 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class BeforeHooks {
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "hasFunc1BeenCalled", targetMethodDescriptor = "()Z")
+ public static void
+ patchHasFunc1BeenCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ ((BeforeHooksTargetContract) thisObject).func1();
+ }
+
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "getTimesCalled", targetMethodDescriptor = "()Ljava/lang/Integer;")
+ public static void
+ patchHasBeenCalled(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ // Invoke static method getTimesCalled() again to pass the test.
+ method.invoke();
+ }
+
+ @MethodHook(type = HookType.BEFORE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.BeforeHooksTarget",
+ targetMethod = "hasFuncWithArgsBeenCalled")
+ public static void
+ patchHasFuncWithArgsBeenCalled(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ if (arguments.length == 2 && arguments[0] instanceof Boolean
+ && arguments[1] instanceof String) {
+ // only if the arguments passed to the hook match the expected argument types and count invoke
+ // the method to pass the test
+ ((BeforeHooksTargetContract) thisObject).setFuncWithArgsCalled((Boolean) arguments[0]);
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
new file mode 100644
index 00000000..31e9733c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// Copyright 2021 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyBeforeHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(BeforeHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ return BeforeHooksTarget()
+}
+
+private fun getNoHooksBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ val originalBytecode = classToBytecode(BeforeHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract
+}
+
+private fun getPatchedBeforeHooksTargetInstance(): BeforeHooksTargetContract {
+ val originalBytecode = classToBytecode(BeforeHooksTarget::class.java)
+ val patchedBytecode = applyBeforeHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${BeforeHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${BeforeHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(BeforeHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as BeforeHooksTargetContract
+}
+
+class BeforeHookTest {
+
+ @Test
+ fun testBeforeHooksOriginal() {
+ assertSelfCheck(getOriginalBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testBeforeHooksNoHooks() {
+ assertSelfCheck(getNoHooksBeforeHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testBeforeHooksPatched() {
+ assertSelfCheck(getPatchedBeforeHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java
new file mode 100644
index 00000000..869e04bf
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTarget.java
@@ -0,0 +1,61 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in BeforeHooks.java applied.
+public class BeforeHooksTarget implements BeforeHooksTargetContract {
+ static private int timesCalled = 0;
+ Map<String, Boolean> results = new HashMap<>();
+ Boolean func1Called = false;
+ Boolean funcWithArgsCalled = false;
+
+ static Integer getTimesCalled() {
+ return ++timesCalled;
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ results.put("hasFunc1BeenCalled", hasFunc1BeenCalled());
+
+ timesCalled = 0;
+ results.put("hasBeenCalledTwice", getTimesCalled() == 2);
+
+ if (!results.containsKey("hasBeenCalledWithArgs")) {
+ results.put("hasBeenCalledWithArgs", hasFuncWithArgsBeenCalled(true, "foo"));
+ }
+
+ return results;
+ }
+
+ public void func1() {
+ func1Called = true;
+ }
+
+ private boolean hasFunc1BeenCalled() {
+ return func1Called;
+ }
+
+ public void setFuncWithArgsCalled(Boolean val) {
+ funcWithArgsCalled = val;
+ }
+
+ private boolean hasFuncWithArgsBeenCalled(Boolean boolArgument, String stringArgument) {
+ return funcWithArgsCalled;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java
new file mode 100644
index 00000000..61f79dcc
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/BeforeHooksTargetContract.java
@@ -0,0 +1,25 @@
+// Copyright 2021 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of BeforeHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface BeforeHooksTargetContract extends DynamicTestContract {
+ void func1();
+
+ void setFuncWithArgsCalled(Boolean val);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java
new file mode 100644
index 00000000..cb811803
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationSpecialCasesTarget.java
@@ -0,0 +1,41 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.Random;
+
+public class CoverageInstrumentationSpecialCasesTarget {
+ public ReturnClass newAfterJump() {
+ if (new Random().nextBoolean()) {
+ throw new RuntimeException("");
+ }
+ return new ReturnClass(new Random().nextBoolean() ? "foo" : "bar");
+ }
+
+ public int newAndTryCatch() {
+ new Random();
+ try {
+ new Random();
+ return 2;
+ } catch (RuntimeException e) {
+ new Random();
+ return 1;
+ }
+ }
+
+ public static class ReturnClass {
+ public ReturnClass(String content) {}
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
new file mode 100644
index 00000000..7502481d
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTarget.java
@@ -0,0 +1,67 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CoverageInstrumentationTarget implements DynamicTestContract {
+ volatile int int1 = 3;
+ volatile int int2 = 213234;
+
+ @Override
+ public Map<String, Boolean> selfCheck() {
+ HashMap<String, Boolean> results = new HashMap<>();
+
+ results.put("for0", false);
+ results.put("for1", false);
+ results.put("for2", false);
+ results.put("for3", false);
+ results.put("for4", false);
+ results.put("foobar", false);
+ results.put("baz", true);
+
+ if (int1 < int2) {
+ results.put("block1", true);
+ } else {
+ results.put("block2", false);
+ }
+
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < 5; j++) {
+ results.put("for" + j, i != 0);
+ }
+ }
+
+ foo(results);
+
+ return results;
+ }
+
+ private void foo(HashMap<String, Boolean> results) {
+ bar(results);
+ }
+
+ // The use of Map instead of HashMap is deliberate here: Since Map#put can throw exceptions, the
+ // invocation should be instrumented for coverage.
+ private void bar(Map<String, Boolean> results) {
+ results.put("foobar", true);
+ }
+
+ @SuppressWarnings("unused")
+ private void baz(HashMap<String, Boolean> results) {
+ results.put("baz", false);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
new file mode 100644
index 00000000..15c88f4c
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/CoverageInstrumentationTest.kt
@@ -0,0 +1,141 @@
+// Copyright 2021 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+import kotlin.test.assertEquals
+
+private fun applyInstrumentation(bytecode: ByteArray): ByteArray {
+ return EdgeCoverageInstrumentor(0, MockCoverageMap::class.java).instrument(bytecode)
+}
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return CoverageInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(CoverageInstrumentationTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${CoverageInstrumentationTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${CoverageInstrumentationTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(CoverageInstrumentationTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as DynamicTestContract
+}
+
+private fun assertControlFlow(expectedLocations: List<Int>) {
+ assertEquals(expectedLocations, MockCoverageMap.locations.toList())
+}
+
+class CoverageInstrumentationTest {
+
+ private val constructorReturn = 0
+ private val ifFirstBranch = 1
+ @Suppress("unused")
+ private val ifSecondBranch = 2
+ private val ifEnd = 3
+ private val outerForCondition = 4
+ private val innerForBodyIfFirstRun = 6
+ private val innerForBodyIfSecondRun = 5
+ private val innerForIncrementCounter = 7
+ private val outerForIncrementCounter = 8
+ private val afterFooInvocation = 9
+ private val beforeReturn = 10
+ private val fooAfterBarInvocation = 11
+ private val fooBeforeReturn = 12
+ private val barAfterMapPutInvocation = 13
+ private val barBeforeReturn = 14
+ @Suppress("unused")
+ private val bazReturn = 15
+
+ @Test
+ fun testOriginal() {
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockCoverageMap.clear()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+
+ val innerForFirstRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForBodyIfFirstRun, innerForIncrementCounter))
+ }
+ }.toList()
+ val innerForSecondRunControlFlow = mutableListOf<Int>().apply {
+ repeat(5) {
+ addAll(listOf(innerForBodyIfSecondRun, innerForIncrementCounter))
+ }
+ }.toList()
+ val outerForControlFlow =
+ listOf(outerForCondition) +
+ innerForFirstRunControlFlow +
+ listOf(outerForIncrementCounter, outerForCondition) +
+ innerForSecondRunControlFlow +
+ listOf(outerForIncrementCounter)
+
+ assertControlFlow(
+ listOf(constructorReturn, ifFirstBranch, ifEnd) +
+ outerForControlFlow +
+ listOf(
+ barAfterMapPutInvocation, barBeforeReturn,
+ fooAfterBarInvocation, fooBeforeReturn,
+ afterFooInvocation, beforeReturn
+ )
+ )
+ }
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ @Test
+ fun testCounters() {
+ MockCoverageMap.clear()
+
+ val target = getInstrumentedInstrumentationTargetInstance()
+ // The constructor of the target is run only once.
+ val takenOnceEdge = constructorReturn
+ // Control flows through the first if branch once per run.
+ val takenOnEveryRunEdge = ifFirstBranch
+
+ var lastCounter = 0.toUByte()
+ for (i in 1..600) {
+ assertSelfCheck(target)
+ assertEquals(1, MockCoverageMap.mem[takenOnceEdge])
+ // Verify that the counter increments, but is never zero.
+ val expectedCounter = (lastCounter + 1U).toUByte().takeUnless { it == 0.toUByte() }
+ ?: (lastCounter + 2U).toUByte()
+ lastCounter = expectedCounter
+ val actualCounter = MockCoverageMap.mem[takenOnEveryRunEdge].toUByte()
+ assertEquals(expectedCounter, actualCounter, "After $i runs:")
+ }
+ }
+
+ @Test
+ fun testSpecialCases() {
+ val originalBytecode = classToBytecode(CoverageInstrumentationSpecialCasesTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${CoverageInstrumentationSpecialCasesTarget::class.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${CoverageInstrumentationSpecialCasesTarget::class.simpleName}.patched.class").writeBytes(
+ patchedBytecode
+ )
+ val patchedClass = bytecodeToClass(CoverageInstrumentationSpecialCasesTarget::class.java.name, patchedBytecode)
+ // Trigger a class load
+ patchedClass.declaredMethods
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
new file mode 100644
index 00000000..e7e1feba
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DescriptorUtilsTest.kt
@@ -0,0 +1,73 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.utils.descriptor
+import org.junit.Test
+import kotlin.test.assertEquals
+
+class DescriptorUtilsTest {
+
+ @Test
+ fun testClassDescriptor() {
+ assertEquals("V", java.lang.Void::class.javaPrimitiveType?.descriptor)
+ assertEquals("J", java.lang.Long::class.javaPrimitiveType?.descriptor)
+ assertEquals("[[[Z", Array<Array<BooleanArray>>::class.java.descriptor)
+ assertEquals("[Ljava/lang/String;", Array<String>::class.java.descriptor)
+ }
+
+ @Test
+ fun testExtractInternalClassName() {
+ assertEquals("java/lang/String", extractInternalClassName("Ljava/lang/String;"))
+ assertEquals("[Ljava/lang/String;", extractInternalClassName("[Ljava/lang/String;"))
+ assertEquals("B", extractInternalClassName("B"))
+ }
+
+ @Test
+ fun testExtractTypeDescriptors() {
+ val testCases = listOf(
+ Triple(
+ String::class.java.getMethod("equals", Object::class.java),
+ listOf("Ljava/lang/Object;"),
+ "Z"
+ ),
+ Triple(
+ String::class.java.getMethod("regionMatches", Boolean::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java, Int::class.javaPrimitiveType, Integer::class.javaPrimitiveType),
+ listOf("Z", "I", "Ljava/lang/String;", "I", "I"),
+ "Z"
+ ),
+ Triple(
+ String::class.java.getMethod("getChars", Integer::class.javaPrimitiveType, Int::class.javaPrimitiveType, CharArray::class.java, Int::class.javaPrimitiveType),
+ listOf("I", "I", "[C", "I"),
+ "V"
+ ),
+ Triple(
+ String::class.java.getMethod("subSequence", Integer::class.javaPrimitiveType, Integer::class.javaPrimitiveType),
+ listOf("I", "I"),
+ "Ljava/lang/CharSequence;"
+ ),
+ Triple(
+ String::class.java.getConstructor(),
+ emptyList(),
+ "V"
+ )
+ )
+ for ((executable, parameterDescriptors, returnTypeDescriptor) in testCases) {
+ val descriptor = executable.descriptor
+ assertEquals(extractParameterTypeDescriptors(descriptor), parameterDescriptors)
+ assertEquals(extractReturnTypeDescriptor(descriptor), returnTypeDescriptor)
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java
new file mode 100644
index 00000000..163b226a
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/DynamicTestContract.java
@@ -0,0 +1,21 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.Map;
+
+public interface DynamicTestContract {
+ Map<String, Boolean> selfCheck();
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
new file mode 100644
index 00000000..7e7c31c9
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/HookValidationTest.kt
@@ -0,0 +1,38 @@
+// Copyright 2021 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.instrumentor
+
+import com.code_intelligence.jazzer.api.MethodHook
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+class HookValidationTest {
+ @Test
+ fun testValidHooks() {
+ assertEquals(6, loadHooks(ValidHookMocks::class.java).size)
+ }
+
+ @Test
+ fun testInvalidHooks() {
+ for (method in InvalidHookMocks::class.java.methods) {
+ if (method.isAnnotationPresent(MethodHook::class.java)) {
+ assertFailsWith<IllegalArgumentException>("Expected ${method.name} to be an invalid hook") {
+ Hook.verifyAndGetHook(method, method.declaredAnnotations.first() as MethodHook)
+ }
+ }
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java
new file mode 100644
index 00000000..2723ad6e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/InvalidHookMocks.java
@@ -0,0 +1,61 @@
+// Copyright 2021 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+class InvalidHookMocks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void incorrectHookIdType(
+ MethodHandle method, String thisObject, Object[] arguments, long hookId) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ private static void invalidAfterHook(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, Boolean returnValue) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public void invalidAfterHook2(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z")
+ public static String
+ incorrectReturnType(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return "foo";
+ }
+
+ @MethodHook(
+ type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static boolean
+ invalidReplaceHook2(MethodHandle method, Integer thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.StringBuilder",
+ targetMethod = "<init>", targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static Object
+ invalidReturnType(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ return null;
+ }
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String",
+ targetMethod = "startsWith", targetMethodDescriptor = "(Ljava/lang/String;)Z")
+ public static void
+ primitiveReturnValueMustBeWrapped(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
new file mode 100644
index 00000000..787ea493
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockCoverageMap.java
@@ -0,0 +1,45 @@
+// Copyright 2021 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.instrumentor;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class MockCoverageMap {
+ public static final int SIZE = 65536;
+ public static final ByteBuffer mem = ByteBuffer.allocate(SIZE);
+ public static int prev_location = 0; // is used in byte code directly
+
+ private static final ByteBuffer previous_mem = ByteBuffer.allocate(SIZE);
+ public static ArrayList<Integer> locations = new ArrayList<>();
+
+ public static void updated() {
+ int updated_pos = -1;
+ for (int i = 0; i < SIZE; i++) {
+ if (previous_mem.get(i) != mem.get(i)) {
+ updated_pos = i;
+ }
+ }
+ locations.add(updated_pos);
+ System.arraycopy(mem.array(), 0, previous_mem.array(), 0, SIZE);
+ }
+
+ public static void clear() {
+ Arrays.fill(mem.array(), (byte) 0);
+ Arrays.fill(previous_mem.array(), (byte) 0);
+ locations.clear();
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java
new file mode 100644
index 00000000..ad659da0
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/MockTraceDataFlowCallbacks.java
@@ -0,0 +1,106 @@
+// Copyright 2021 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.instrumentor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class MockTraceDataFlowCallbacks {
+ private static List<String> hookCalls;
+ private static int assertedCalls;
+
+ public static void init() {
+ hookCalls = new ArrayList<>();
+ assertedCalls = 0;
+ }
+
+ public static boolean hookCall(String expectedCall) {
+ if (assertedCalls >= hookCalls.size()) {
+ System.err.println("Not seen (" + hookCalls.size() + " calls, but " + (assertedCalls + 1)
+ + " expected): " + expectedCall);
+ return false;
+ }
+
+ if (!hookCalls.get(assertedCalls).equals(expectedCall)) {
+ System.err.println("Call " + expectedCall + " not seen, got " + hookCalls.get(assertedCalls));
+ return false;
+ }
+
+ assertedCalls++;
+ return true;
+ }
+
+ public static boolean finish() {
+ if (assertedCalls == hookCalls.size())
+ return true;
+ System.err.println("The following calls were not asserted:");
+ for (int i = assertedCalls; i < hookCalls.size(); i++) {
+ System.err.println(hookCalls.get(i));
+ }
+
+ return false;
+ }
+
+ public static void traceCmpLong(long arg1, long arg2, int pc) {
+ hookCalls.add("LCMP: " + Math.min(arg1, arg2) + ", " + Math.max(arg1, arg2));
+ }
+
+ public static void traceCmpInt(int arg1, int arg2, int pc) {
+ hookCalls.add("ICMP: " + Math.min(arg1, arg2) + ", " + Math.max(arg1, arg2));
+ }
+
+ public static void traceConstCmpInt(int arg1, int arg2, int pc) {
+ hookCalls.add("CICMP: " + arg1 + ", " + arg2);
+ }
+
+ public static void traceDivInt(int val, int pc) {
+ hookCalls.add("IDIV: " + val);
+ }
+
+ public static void traceDivLong(long val, int pc) {
+ hookCalls.add("LDIV: " + val);
+ }
+
+ public static void traceGep(long idx, int pc) {
+ hookCalls.add("GEP: " + idx);
+ }
+
+ public static void traceSwitch(long switchValue, long[] libfuzzerCaseValues, int pc) {
+ if (libfuzzerCaseValues.length < 3
+ // number of case values must match length
+ || libfuzzerCaseValues[0] != libfuzzerCaseValues.length - 2
+ // bit size of case values is always 32 (int)
+ || libfuzzerCaseValues[1] != 32) {
+ hookCalls.add("INVALID_SWITCH");
+ return;
+ }
+
+ StringBuilder builder = new StringBuilder("SWITCH: " + switchValue + ", (");
+ for (int i = 2; i < libfuzzerCaseValues.length; i++) {
+ builder.append(libfuzzerCaseValues[i]);
+ builder.append(", ");
+ }
+ builder.append(")");
+ hookCalls.add(builder.toString());
+ }
+
+ public static int traceCmpLongWrapper(long value1, long value2, int pc) {
+ traceCmpLong(value1, value2, pc);
+ // Long.compare serves as a substitute for the lcmp opcode here
+ // (behaviour is the same)
+ return Long.compare(value1, value2);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
new file mode 100644
index 00000000..f286d03f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/PatchTestUtils.kt
@@ -0,0 +1,53 @@
+// Copyright 2021 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.instrumentor
+
+fun classToBytecode(targetClass: Class<*>): ByteArray {
+ return ClassLoader
+ .getSystemClassLoader()
+ .getResourceAsStream("${targetClass.name.replace('.', '/')}.class")!!
+ .use {
+ it.readBytes()
+ }
+}
+
+fun bytecodeToClass(name: String, bytecode: ByteArray): Class<*> {
+ return BytecodeClassLoader(name, bytecode).loadClass(name)
+}
+
+/**
+ * A ClassLoader that dynamically loads a single specified class from byte code and delegates all other class loads to
+ * its own ClassLoader.
+ */
+class BytecodeClassLoader(val className: String, private val classBytecode: ByteArray) :
+ ClassLoader(BytecodeClassLoader::class.java.classLoader) {
+ override fun loadClass(name: String): Class<*> {
+ if (name != className)
+ return super.loadClass(name)
+
+ return defineClass(className, classBytecode, 0, classBytecode.size)
+ }
+}
+
+fun assertSelfCheck(target: DynamicTestContract, shouldPass: Boolean = true) {
+ val results = target.selfCheck()
+ for ((test, passed) in results) {
+ if (shouldPass) {
+ assert(passed) { "$test should pass" }
+ } else {
+ assert(!passed) { "$test should not pass" }
+ }
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
new file mode 100644
index 00000000..a71e1180
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooks.java
@@ -0,0 +1,109 @@
+// Copyright 2021 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class ReplaceHooks {
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue1")
+ public static boolean
+ patchShouldReturnTrue1(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue2")
+ public static Boolean
+ patchShouldReturnTrue2(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnTrue3")
+ public static Object
+ patchShouldReturnTrue3(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse1")
+ public static Boolean
+ patchShouldReturnFalse1(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return false;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse2")
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnFalse3")
+ public static Object
+ patchShouldReturnFalse2(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return false;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldReturnReversed",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/String;")
+ public static String
+ patchShouldReturnReversed(
+ MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return new StringBuilder((String) arguments[0]).reverse().toString();
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldIncrement")
+ public static int
+ patchShouldIncrement(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return ((int) arguments[0]) + 1;
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "shouldCallPass")
+ public static void
+ patchShouldCallPass(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ ((ReplaceHooksTargetContract) thisObject).pass("shouldCallPass");
+ }
+
+ @MethodHook(type = HookType.REPLACE,
+ targetClassName = "com.code_intelligence.jazzer.instrumentor.ReplaceHooksTarget",
+ targetMethod = "idempotent", targetMethodDescriptor = "(I)I")
+ public static int
+ patchIdempotent(MethodHandle method, Object thisObject, Object[] arguments, int hookId)
+ throws Throwable {
+ // Iterate the function twice to pass the test.
+ int input = (int) arguments[0];
+ int temp = (int) method.invokeWithArguments(thisObject, input);
+ return (int) method.invokeWithArguments(thisObject, temp);
+ }
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.util.AbstractList",
+ targetMethod = "get", targetMethodDescriptor = "(I)Ljava/lang/Object;")
+ public static Object
+ patchAbstractListGet(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
new file mode 100644
index 00000000..76fb53e5
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksPatchTest.kt
@@ -0,0 +1,63 @@
+// Copyright 2021 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyReplaceHooks(bytecode: ByteArray): ByteArray {
+ return HookInstrumentor(loadHooks(ReplaceHooks::class.java), false).instrument(bytecode)
+}
+
+private fun getOriginalReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ return ReplaceHooksTarget()
+}
+
+private fun getNoHooksReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java)
+ // Let the bytecode pass through the hooking logic, but don't apply any hooks.
+ val patchedBytecode = HookInstrumentor(emptyList(), false).instrument(originalBytecode)
+ val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract
+}
+
+private fun getPatchedReplaceHooksTargetInstance(): ReplaceHooksTargetContract {
+ val originalBytecode = classToBytecode(ReplaceHooksTarget::class.java)
+ val patchedBytecode = applyReplaceHooks(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${ReplaceHooksTarget::class.java.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${ReplaceHooksTarget::class.java.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(ReplaceHooksTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as ReplaceHooksTargetContract
+}
+
+class ReplaceHookTest {
+
+ @Test
+ fun testReplaceHooksOriginal() {
+ assertSelfCheck(getOriginalReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testReplaceHooksNoHooks() {
+ assertSelfCheck(getNoHooksReplaceHooksTargetInstance(), false)
+ }
+
+ @Test
+ fun testReplaceHooksPatched() {
+ assertSelfCheck(getPatchedReplaceHooksTargetInstance(), true)
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
new file mode 100644
index 00000000..7a4b89f8
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTarget.java
@@ -0,0 +1,120 @@
+// Copyright 2021 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.instrumentor;
+
+import java.security.SecureRandom;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+// selfCheck() only passes with the hooks in ReplaceHooks.java applied.
+public class ReplaceHooksTarget implements ReplaceHooksTargetContract {
+ Map<String, Boolean> results = new HashMap<>();
+
+ public static boolean shouldReturnTrue3() {
+ // return true;
+ return false;
+ }
+
+ public Map<String, Boolean> selfCheck() {
+ results = new HashMap<>();
+
+ results.put("shouldReturnTrue1", shouldReturnTrue1());
+ results.put("shouldReturnTrue2", shouldReturnTrue2());
+ results.put("shouldReturnTrue3", shouldReturnTrue3());
+ try {
+ boolean notTrue = false;
+ results.put("shouldReturnFalse1", notTrue);
+ if (!results.get("shouldReturnFalse1"))
+ results.put("shouldReturnFalse1", !shouldReturnFalse1());
+ boolean notFalse = true;
+ results.put("shouldReturnFalse2", !shouldReturnFalse2() && notFalse);
+ results.put("shouldReturnFalse3", !shouldReturnFalse3());
+ } catch (Exception e) {
+ boolean notTrue = false;
+ results.put("shouldNotBeExecuted", notTrue);
+ }
+ results.put("shouldReturnReversed", shouldReturnReversed("foo").equals("oof"));
+ results.put("shouldIncrement", shouldIncrement(5) == 6);
+ results.put("verifyIdentity", verifyIdentity());
+
+ results.put("shouldCallPass", false);
+ if (!results.get("shouldCallPass")) {
+ shouldCallPass();
+ }
+
+ AbstractList<Boolean> boolList = new ArrayList<>();
+ boolList.add(false);
+ results.put("arrayListGet", boolList.get(0));
+
+ return results;
+ }
+
+ public boolean shouldReturnTrue1() {
+ // return true;
+ return false;
+ }
+
+ public boolean shouldReturnTrue2() {
+ // return true;
+ return false;
+ }
+
+ protected Boolean shouldReturnFalse1() {
+ // return false;
+ return true;
+ }
+
+ Boolean shouldReturnFalse2() {
+ // return false;
+ return true;
+ }
+
+ public Boolean shouldReturnFalse3() {
+ // return false;
+ return true;
+ }
+
+ public String shouldReturnReversed(String input) {
+ // return new StringBuilder(input).reverse().toString();
+ return input;
+ }
+
+ public int shouldIncrement(int input) {
+ // return input + 1;
+ return input;
+ }
+
+ private void shouldCallPass() {
+ // pass("shouldCallPass");
+ }
+
+ private boolean verifyIdentity() {
+ SecureRandom rand = new SecureRandom();
+ int input = rand.nextInt();
+ // return idempotent(idempotent(input)) == input;
+ return idempotent(input) == input;
+ }
+
+ private int idempotent(int input) {
+ int secret = 0x12345678;
+ return input ^ secret;
+ }
+
+ public void pass(String test) {
+ results.put(test, true);
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java
new file mode 100644
index 00000000..e3dff93e
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ReplaceHooksTargetContract.java
@@ -0,0 +1,23 @@
+// Copyright 2021 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.instrumentor;
+
+/**
+ * Helper interface used to call methods on instances of ReplaceHooksTarget classes loaded via
+ * different class loaders.
+ */
+public interface ReplaceHooksTargetContract extends DynamicTestContract {
+ void pass(String test);
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
new file mode 100644
index 00000000..48f16e60
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTarget.java
@@ -0,0 +1,152 @@
+// Copyright 2021 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.instrumentor;
+
+import java.nio.ByteBuffer;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.Vector;
+
+public class TraceDataFlowInstrumentationTarget implements DynamicTestContract {
+ volatile long long1 = 1;
+ volatile long long2 = 1;
+ volatile long long3 = 2;
+ volatile long long4 = 3;
+
+ volatile int int1 = 4;
+ volatile int int2 = 4;
+ volatile int int3 = 6;
+ volatile int int4 = 5;
+
+ volatile int switchValue = 1200;
+
+ @Override
+ public Map<String, Boolean> selfCheck() {
+ Map<String, Boolean> results = new HashMap<>();
+
+ results.put("longCompareEq", long1 == long2);
+ results.put("longCompareNe", long3 != long4);
+
+ results.put("intCompareEq", int1 == int2);
+ results.put("intCompareNe", int3 != int4);
+ results.put("intCompareLt", int4 < int3);
+ results.put("intCompareLe", int4 <= int3);
+ results.put("intCompareGt", int3 > int4);
+ results.put("intCompareGe", int3 >= int4);
+
+ // Not instrumented since all case values are non-negative and < 256.
+ switch (switchValue) {
+ case 119:
+ case 120:
+ case 121:
+ results.put("tableSwitchUninstrumented", false);
+ break;
+ default:
+ results.put("tableSwitchUninstrumented", true);
+ }
+
+ // Not instrumented since all case values are non-negative and < 256.
+ switch (switchValue) {
+ case 1:
+ case 200:
+ results.put("lookupSwitchUninstrumented", false);
+ break;
+ default:
+ results.put("lookupSwitchUninstrumented", true);
+ }
+
+ results.put("emptySwitchUninstrumented", false);
+ switch (switchValue) {
+ default:
+ results.put("emptySwitchUninstrumented", true);
+ }
+
+ switch (switchValue) {
+ case 1000:
+ case 1001:
+ // case 1002: The tableswitch instruction will contain a gap case for 1002.
+ case 1003:
+ results.put("tableSwitch", false);
+ break;
+ default:
+ results.put("tableSwitch", true);
+ }
+
+ switch (-switchValue) {
+ case -1200:
+ results.put("lookupSwitch", true);
+ break;
+ case -1:
+ case -10:
+ case -1000:
+ case 200:
+ default:
+ results.put("lookupSwitch", false);
+ }
+
+ results.put("intDiv", (int3 / 2) == 3);
+
+ results.put("longDiv", (long4 / 2) == 1);
+
+ String[] referenceArray = {"foo", "foo", "bar"};
+ boolean[] boolArray = {false, false, true};
+ byte[] byteArray = {0, 0, 2};
+ char[] charArray = {0, 0, 0, 3};
+ double[] doubleArray = {0, 0, 0, 0, 4};
+ float[] floatArray = {0, 0, 0, 0, 0, 5};
+ int[] intArray = {0, 0, 0, 0, 0, 0, 6};
+ long[] longArray = {0, 0, 0, 0, 0, 0, 0, 7};
+ short[] shortArray = {0, 0, 0, 0, 0, 0, 0, 0, 8};
+
+ results.put("referenceArrayGep", referenceArray[2].equals("bar"));
+ results.put("boolArrayGep", boolArray[2]);
+ results.put("byteArrayGep", byteArray[2] == 2);
+ results.put("charArrayGep", charArray[3] == 3);
+ results.put("doubleArrayGep", doubleArray[4] == 4);
+ results.put("floatArrayGep", floatArray[5] == 5);
+ results.put("intArrayGep", intArray[6] == 6);
+ results.put("longArrayGep", longArray[7] == 7);
+ results.put("shortArrayGep", shortArray[8] == 8);
+
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ buffer.get(2);
+ buffer.getChar(3);
+ buffer.getDouble(4);
+ buffer.getFloat(5);
+ buffer.getInt(6);
+ buffer.getLong(7);
+ buffer.getShort(8);
+
+ "foobarbazbat".charAt(9);
+ "foobarbazbat".codePointAt(10);
+ new StringBuilder("foobarbazbat").charAt(11);
+
+ (new Vector<>(Collections.nCopies(20, "foo"))).get(12);
+ (new ArrayList<>(Collections.nCopies(20, "foo"))).get(13);
+ Stack<String> stack = new Stack<>();
+ for (int i = 0; i < 20; i++) stack.push("foo");
+ stack.get(14);
+ stack.get(15);
+ ((AbstractList<String>) stack).get(16);
+ ((List<String>) stack).get(17);
+
+ return results;
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
new file mode 100644
index 00000000..c6fd218f
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/TraceDataFlowInstrumentationTest.kt
@@ -0,0 +1,145 @@
+// Copyright 2021 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.instrumentor
+
+import org.junit.Test
+import java.io.File
+
+private fun applyInstrumentation(bytecode: ByteArray): ByteArray {
+ return TraceDataFlowInstrumentor(
+ setOf(
+ InstrumentationType.CMP,
+ InstrumentationType.DIV,
+ InstrumentationType.GEP
+ ),
+ MockTraceDataFlowCallbacks::class.java
+ ).instrument(bytecode)
+}
+
+private fun getOriginalInstrumentationTargetInstance(): DynamicTestContract {
+ return TraceDataFlowInstrumentationTarget()
+}
+
+private fun getInstrumentedInstrumentationTargetInstance(): DynamicTestContract {
+ val originalBytecode = classToBytecode(TraceDataFlowInstrumentationTarget::class.java)
+ val patchedBytecode = applyInstrumentation(originalBytecode)
+ // Make the patched class available in bazel-testlogs/.../test.outputs for manual inspection.
+ val outDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+ File("$outDir/${TraceDataFlowInstrumentationTarget::class.simpleName}.class").writeBytes(originalBytecode)
+ File("$outDir/${TraceDataFlowInstrumentationTarget::class.simpleName}.patched.class").writeBytes(patchedBytecode)
+ val patchedClass = bytecodeToClass(TraceDataFlowInstrumentationTarget::class.java.name, patchedBytecode)
+ return patchedClass.getDeclaredConstructor().newInstance() as DynamicTestContract
+}
+
+class TraceDataFlowInstrumentationTest {
+
+ @Test
+ fun testOriginal() {
+ MockTraceDataFlowCallbacks.init()
+ assertSelfCheck(getOriginalInstrumentationTargetInstance())
+ assert(MockTraceDataFlowCallbacks.finish())
+ }
+
+ @Test
+ fun testInstrumented() {
+ MockTraceDataFlowCallbacks.init()
+ assertSelfCheck(getInstrumentedInstrumentationTargetInstance())
+ listOf(
+ // long compares
+ "LCMP: 1, 1",
+ "LCMP: 2, 3",
+ // int compares
+ "ICMP: 4, 4",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ "ICMP: 5, 6",
+ // tableswitch with gap
+ "SWITCH: 1200, (1000, 1001, 1003, )",
+ // lookupswitch
+ "SWITCH: -1200, (200, -1200, -1000, -10, -1, )",
+ // (6 / 2) == 3
+ "IDIV: 2",
+ "ICMP: 3, 3",
+ // (3 / 2) == 1
+ "LDIV: 2",
+ "LCMP: 1, 1",
+ // referenceArray[2]
+ "GEP: 2",
+ // boolArray[2]
+ "GEP: 2",
+ // byteArray[2] == 2
+ "GEP: 2",
+ "ICMP: 2, 2",
+ // charArray[3] == 3
+ "GEP: 3",
+ "ICMP: 3, 3",
+ // doubleArray[4] == 4
+ "GEP: 4",
+ // floatArray[5] == 5
+ "GEP: 5",
+ "CICMP: 0, 0",
+ // intArray[6] == 6
+ "GEP: 6",
+ "ICMP: 6, 6",
+ // longArray[7] == 7
+ "GEP: 7",
+ "LCMP: 7, 7",
+ // shortArray[8] == 8
+ "GEP: 8",
+ "ICMP: 8, 8",
+
+ "GEP: 2",
+ "GEP: 3",
+ "GEP: 4",
+ "GEP: 5",
+ "GEP: 6",
+ "GEP: 7",
+ "GEP: 8",
+ "GEP: 9",
+ "GEP: 10",
+ "GEP: 11",
+ "GEP: 12",
+ "GEP: 13",
+ "ICMP: 0, 20",
+ "ICMP: 1, 20",
+ "ICMP: 2, 20",
+ "ICMP: 3, 20",
+ "ICMP: 4, 20",
+ "ICMP: 5, 20",
+ "ICMP: 6, 20",
+ "ICMP: 7, 20",
+ "ICMP: 8, 20",
+ "ICMP: 9, 20",
+ "ICMP: 10, 20",
+ "ICMP: 11, 20",
+ "ICMP: 12, 20",
+ "ICMP: 13, 20",
+ "ICMP: 14, 20",
+ "ICMP: 15, 20",
+ "ICMP: 16, 20",
+ "ICMP: 17, 20",
+ "ICMP: 18, 20",
+ "ICMP: 19, 20",
+ "ICMP: 20, 20",
+ "GEP: 14",
+ "GEP: 15",
+ "GEP: 16",
+ "GEP: 17",
+ ).forEach { assert(MockTraceDataFlowCallbacks.hookCall(it)) }
+ assert(MockTraceDataFlowCallbacks.finish())
+ }
+}
diff --git a/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java
new file mode 100644
index 00000000..06bed141
--- /dev/null
+++ b/agent/src/test/java/com/code_intelligence/jazzer/instrumentor/ValidHookMocks.java
@@ -0,0 +1,49 @@
+// Copyright 2021 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.instrumentor;
+
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+class ValidHookMocks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validBeforeHook(
+ MethodHandle method, String thisObject, Object[] arguments, int hookId) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validAfterHook(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, Boolean returnValue) {}
+
+ @MethodHook(type = HookType.AFTER, targetClassName = "java.lang.String", targetMethod = "equals")
+ public static void validAfterHook2(MethodHandle method, String thisObject, Object[] arguments,
+ int hookId, boolean returnValue) {}
+
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equals", targetMethodDescriptor = "(Ljava/lang/Object;)Z")
+ public static Boolean
+ validReplaceHook(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+
+ @MethodHook(
+ type = HookType.REPLACE, targetClassName = "java.lang.String", targetMethod = "equals")
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.lang.String",
+ targetMethod = "equalsIgnoreCase")
+ public static boolean
+ validReplaceHook2(MethodHandle method, String thisObject, Object[] arguments, int hookId) {
+ return true;
+ }
+}
diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel
new file mode 100644
index 00000000..1e2348c1
--- /dev/null
+++ b/bazel/BUILD.bazel
@@ -0,0 +1,6 @@
+java_library(
+ name = "fuzz_target_test_wrapper",
+ srcs = ["FuzzTargetTestWrapper.java"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@bazel_tools//tools/java/runfiles"],
+)
diff --git a/bazel/FuzzTargetTestWrapper.java b/bazel/FuzzTargetTestWrapper.java
new file mode 100644
index 00000000..59b15844
--- /dev/null
+++ b/bazel/FuzzTargetTestWrapper.java
@@ -0,0 +1,87 @@
+// Copyright 2021 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.
+
+import com.google.devtools.build.runfiles.Runfiles;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class FuzzTargetTestWrapper {
+ public static void main(String[] args) {
+ String driverActualPath;
+ String jarActualPath;
+ Runfiles runfiles;
+ try {
+ runfiles = Runfiles.create();
+ driverActualPath = runfiles.rlocation(rlocationPath(args[0]));
+ jarActualPath = runfiles.rlocation(rlocationPath(args[1]));
+ } catch (IOException | ArrayIndexOutOfBoundsException e) {
+ e.printStackTrace();
+ System.exit(1);
+ return;
+ }
+
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ Map<String, String> environment = processBuilder.environment();
+ // Ensure that Jazzer can find its runfiles.
+ environment.putAll(runfiles.getEnvVars());
+
+ // Crashes will be available as test outputs. These are cleared on the next run,
+ // so this is only useful for examples.
+ String outputDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR");
+ List<String> command =
+ Stream
+ .concat(Stream.of(driverActualPath, String.format("-artifact_prefix=%s/", outputDir),
+ String.format("--reproducer_path=%s", outputDir), "-seed=2735196724",
+ String.format("--cp=%s", jarActualPath)),
+ Arrays.stream(args).skip(2))
+ .collect(Collectors.toList());
+ processBuilder.inheritIO();
+ processBuilder.command(command);
+
+ try {
+ int exitCode = processBuilder.start().waitFor();
+ // Assert that we either found a crash in Java (exit code 77) or a sanitizer crash (exit code
+ // 76).
+ if (exitCode != 76 && exitCode != 77) {
+ System.exit(3);
+ }
+ String[] outputFiles = new File(outputDir).list();
+ if (outputFiles == null) {
+ System.exit(4);
+ }
+ // Verify that libFuzzer dumped a crashing input.
+ if (Arrays.stream(outputFiles).noneMatch(name -> name.startsWith("crash-"))) {
+ System.exit(5);
+ }
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ System.exit(2);
+ }
+ System.exit(0);
+ }
+
+ // Turns the result of Bazel's `$(rootpath ...)` into the correct format for rlocation.
+ private static String rlocationPath(String rootpath) {
+ if (rootpath.startsWith("external/")) {
+ return rootpath.substring("external/".length());
+ } else {
+ return "jazzer/" + rootpath;
+ }
+ }
+}
diff --git a/bazel/cc.bzl b/bazel/cc.bzl
new file mode 100644
index 00000000..65d298d5
--- /dev/null
+++ b/bazel/cc.bzl
@@ -0,0 +1,80 @@
+# Copyright 2021 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.
+
+def _add_cxxopt_std_17_impl(settings, attr):
+ STD_CXX_17_CXXOPTS = ["/std:c++17" if attr.is_windows else "-std=c++17"]
+ return {
+ "//command_line_option:cxxopt": settings["//command_line_option:cxxopt"] + STD_CXX_17_CXXOPTS,
+ }
+
+_add_cxxopt_std_17 = transition(
+ implementation = _add_cxxopt_std_17_impl,
+ inputs = [
+ "//command_line_option:cxxopt",
+ ],
+ outputs = [
+ "//command_line_option:cxxopt",
+ ],
+)
+
+def _cc_17_library_impl(ctx):
+ library = ctx.attr.library[0]
+ return [
+ # Workaround for https://github.com/bazelbuild/bazel/issues/9442.
+ DefaultInfo(
+ data_runfiles = library[DefaultInfo].data_runfiles,
+ default_runfiles = library[DefaultInfo].default_runfiles,
+ files = library[DefaultInfo].files,
+ ),
+ library[CcInfo],
+ ]
+
+_cc_17_library = rule(
+ implementation = _cc_17_library_impl,
+ attrs = {
+ "is_windows": attr.bool(),
+ "library": attr.label(
+ cfg = _add_cxxopt_std_17,
+ mandatory = True,
+ providers = [CcInfo],
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ },
+ provides = [CcInfo],
+)
+
+# A cc_library that is built with -std=c++17, including all its transitive
+# dependencies. This is redundant while developing Jazzer itself as the .bazelrc
+# sets this flag for all build commands, but is needed when Jazzer is included
+# as an external workspace.
+def cc_17_library(name, visibility = None, **kwargs):
+ library_name = name + "_original_do_not_use_"
+ kwargs.setdefault("tags", []).append("manual")
+ native.cc_library(
+ name = library_name,
+ visibility = ["//visibility:private"],
+ **kwargs
+ )
+
+ _cc_17_library(
+ name = name,
+ is_windows = select({
+ "@platforms//os:windows": True,
+ "//conditions:default": False,
+ }),
+ library = library_name,
+ visibility = visibility,
+ )
diff --git a/bazel/compat.bzl b/bazel/compat.bzl
new file mode 100644
index 00000000..2815db21
--- /dev/null
+++ b/bazel/compat.bzl
@@ -0,0 +1,23 @@
+# Copyright 2021 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.
+
+SKIP_ON_MACOS = select({
+ "@platforms//os:macos": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+})
+
+SKIP_ON_WINDOWS = select({
+ "@platforms//os:windows": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+})
diff --git a/bazel/fuzz_target.bzl b/bazel/fuzz_target.bzl
new file mode 100644
index 00000000..bd90e500
--- /dev/null
+++ b/bazel/fuzz_target.bzl
@@ -0,0 +1,79 @@
+# Copyright 2021 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.
+
+def java_fuzz_target_test(
+ name,
+ target_class = None,
+ deps = [],
+ hook_classes = [],
+ native_libs = [],
+ sanitizer = None,
+ visibility = None,
+ tags = [],
+ fuzzer_args = [],
+ srcs = [],
+ size = None,
+ timeout = None,
+ **kwargs):
+ target_name = name + "_target"
+ deploy_manifest_lines = []
+ if target_class:
+ deploy_manifest_lines.append("Jazzer-Fuzz-Target-Class: %s" % target_class)
+ if hook_classes:
+ deploy_manifest_lines.append("Jazzer-Hook-Classes: %s" % ":".join(hook_classes))
+
+ # Deps can only be specified on java_binary targets with sources, which
+ # excludes e.g. Kotlin libraries wrapped into java_binary via runtime_deps.
+ target_deps = deps + ["//agent:jazzer_api_compile_only"] if srcs else []
+ native.java_binary(
+ name = target_name,
+ srcs = srcs,
+ visibility = ["//visibility:private"],
+ create_executable = False,
+ deploy_manifest_lines = deploy_manifest_lines,
+ deps = target_deps,
+ testonly = True,
+ **kwargs
+ )
+
+ additional_args = []
+
+ if sanitizer == None:
+ driver = "//driver:jazzer_driver"
+ elif sanitizer == "address":
+ driver = "//driver:jazzer_driver_asan"
+ elif sanitizer == "undefined":
+ driver = "//driver:jazzer_driver_ubsan"
+ else:
+ fail("Invalid sanitizer: " + sanitizer)
+
+ native.java_test(
+ name = name,
+ runtime_deps = ["//bazel:fuzz_target_test_wrapper"],
+ size = size or "enormous",
+ timeout = timeout or "moderate",
+ args = [
+ "$(rootpath %s)" % driver,
+ "$(rootpath :%s_deploy.jar)" % target_name,
+ ] + additional_args + fuzzer_args,
+ data = [
+ ":%s_deploy.jar" % target_name,
+ "//agent:jazzer_agent_deploy.jar",
+ driver,
+ ] + native_libs,
+ main_class = "FuzzTargetTestWrapper",
+ use_testrunner = False,
+ tags = tags,
+ visibility = visibility,
+ )
diff --git a/bazel/kotlin.bzl b/bazel/kotlin.bzl
new file mode 100644
index 00000000..873d4392
--- /dev/null
+++ b/bazel/kotlin.bzl
@@ -0,0 +1,51 @@
+# Copyright 2021 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.
+
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
+
+# A kt_jvm_test wrapped in a java_test for Windows compatibility.
+# Workaround for https://github.com/bazelbuild/rules_kotlin/issues/599: rules_kotlin can only create
+# a shell wrapper script for Java targets, no native executable as is required on Windows.
+def wrapped_kt_jvm_test(
+ name,
+ test_class,
+ size = None,
+ tags = None,
+ timeout = None,
+ visibility = None,
+ **kt_jvm_test_args):
+ kt_jvm_test_name = name + "_kt_"
+
+ # Modify a copy of the tags.
+ kt_jvm_test_tags = list(tags) if tags != None else []
+ kt_jvm_test_tags.append("manual")
+ kt_jvm_test(
+ name = kt_jvm_test_name,
+ test_class = test_class,
+ visibility = ["//visibility:private"],
+ tags = kt_jvm_test_tags,
+ **kt_jvm_test_args
+ )
+
+ native.java_test(
+ name = name,
+ size = size,
+ tags = tags,
+ test_class = test_class,
+ timeout = timeout,
+ visibility = visibility,
+ runtime_deps = [
+ ":" + kt_jvm_test_name,
+ ],
+ )
diff --git a/bazelisk-linux-amd64 b/bazelisk-linux-amd64
new file mode 100755
index 00000000..22e49af1
--- /dev/null
+++ b/bazelisk-linux-amd64
Binary files differ
diff --git a/deploy/BUILD.bazel b/deploy/BUILD.bazel
new file mode 100644
index 00000000..29f9a5a1
--- /dev/null
+++ b/deploy/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@rules_jvm_external//:defs.bzl", "java_export")
+load("//:maven.bzl", "JAZZER_API_COORDINATES")
+
+# To publish a new release of the Jazzer API to Maven, run:
+# bazel run --config=maven --define "maven_user=..." --define "maven_password=..." --define gpg_sign=true //:api.publish
+# Build //:api-docs.jar to generate javadocs for the API.
+java_export(
+ name = "api",
+ maven_coordinates = JAZZER_API_COORDINATES,
+ pom_template = "//:jazzer-api.pom",
+ visibility = ["//visibility:public"],
+ runtime_deps = ["//agent/src/main/java/com/code_intelligence/jazzer/api"],
+)
diff --git a/docker/build_all.sh b/docker/build_all.sh
new file mode 100755
index 00000000..21c7c8e2
--- /dev/null
+++ b/docker/build_all.sh
@@ -0,0 +1,19 @@
+# Copyright 2021 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.
+
+set -e
+
+this_dir=$(dirname "$(realpath "$0")")
+docker build --pull -t cifuzz/jazzer "$@" "$this_dir"/jazzer
+docker build -t cifuzz/jazzer-autofuzz "$@" "$this_dir"/jazzer-autofuzz
diff --git a/docker/jazzer-autofuzz/Dockerfile b/docker/jazzer-autofuzz/Dockerfile
new file mode 100644
index 00000000..5d57f2a8
--- /dev/null
+++ b/docker/jazzer-autofuzz/Dockerfile
@@ -0,0 +1,30 @@
+# Copyright 2021 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.
+
+FROM cifuzz/jazzer as jazzer
+
+FROM ubuntu:20.04
+
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update && apt-get install -y curl openjdk-11-jdk-headless
+
+WORKDIR /app
+RUN curl -L 'https://github.com/coursier/coursier/releases/download/v2.0.16/coursier.jar' -o coursier.jar && \
+ chmod +x coursier.jar
+
+COPY entrypoint.sh /app/
+COPY --from=jazzer /app/jazzer_agent_deploy.jar /app/jazzer_driver /app/
+
+WORKDIR /fuzzing
+ENTRYPOINT ["/app/entrypoint.sh"]
diff --git a/docker/jazzer-autofuzz/entrypoint.sh b/docker/jazzer-autofuzz/entrypoint.sh
new file mode 100755
index 00000000..78c57f71
--- /dev/null
+++ b/docker/jazzer-autofuzz/entrypoint.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Copyright 2021 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.
+
+set -e
+
+CP="$(/app/coursier.jar fetch --classpath "$1")"
+/app/jazzer_driver \
+ -artifact_prefix=/fuzzing/ \
+ --reproducer_path=/fuzzing \
+ --cp="$CP" \
+ --autofuzz="$2" \
+ "${@:3}"
diff --git a/docker/jazzer/Dockerfile b/docker/jazzer/Dockerfile
new file mode 100644
index 00000000..56787be7
--- /dev/null
+++ b/docker/jazzer/Dockerfile
@@ -0,0 +1,34 @@
+# Copyright 2021 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.
+
+FROM ubuntu:20.04 AS builder
+
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update && apt-get install -y git python3 python-is-python3 openjdk-11-jdk-headless
+
+WORKDIR /root
+RUN git clone --depth=1 https://github.com/CodeIntelligenceTesting/jazzer.git && \
+ cd jazzer && \
+ # The LLVM toolchain requires ld and ld.gold to exist, but does not use them.
+ touch /usr/bin/ld && \
+ touch /usr/bin/ld.gold && \
+ BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1 \
+ ./bazelisk-linux-amd64 build --config=toolchain --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux \
+ //agent:jazzer_agent_deploy.jar //driver:jazzer_driver
+
+FROM gcr.io/distroless/java
+
+COPY --from=builder /root/jazzer/bazel-bin/agent/jazzer_agent_deploy.jar /root/jazzer/bazel-bin/driver/jazzer_driver /app/
+WORKDIR /fuzzing
+ENTRYPOINT [ "/app/jazzer_driver", "-artifact_prefix=/fuzzing/", "--reproducer_path=/fuzzing" ]
diff --git a/docker/push_all.sh b/docker/push_all.sh
new file mode 100755
index 00000000..59d43718
--- /dev/null
+++ b/docker/push_all.sh
@@ -0,0 +1,21 @@
+# Copyright 2021 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.
+
+set -e
+
+this_dir=$(dirname "$(realpath "$0")")
+"$this_dir"/build_all.sh --no-cache
+
+docker push cifuzz/jazzer
+docker push cifuzz/jazzer-autofuzz
diff --git a/driver/BUILD.bazel b/driver/BUILD.bazel
new file mode 100644
index 00000000..becd4fe1
--- /dev/null
+++ b/driver/BUILD.bazel
@@ -0,0 +1,265 @@
+load("//bazel:cc.bzl", "cc_17_library")
+
+cc_library(
+ name = "sanitizer_hooks_with_pc",
+ srcs = ["sanitizer_hooks_with_pc.cpp"],
+ hdrs = ["sanitizer_hooks_with_pc.h"],
+ linkstatic = True,
+)
+
+cc_test(
+ name = "sanitizer_hooks_with_pc_test",
+ size = "small",
+ srcs = ["sanitizer_hooks_with_pc_test.cpp"],
+ deps = [
+ ":sanitizer_hooks_with_pc",
+ "@googletest//:gtest",
+ "@googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "fuzzed_data_provider",
+ srcs = [
+ "fuzzed_data_provider.cpp",
+ ],
+ hdrs = [
+ "fuzzed_data_provider.h",
+ ],
+ visibility = [
+ "//agent/src/main/native/com/code_intelligence/jazzer/replay:__pkg__",
+ ],
+ deps = [
+ "@com_google_absl//absl/strings:str_format",
+ "@fmeum_rules_jni//jni",
+ ],
+)
+
+cc_library(
+ name = "jvm_tooling_lib",
+ srcs = [
+ "coverage_tracker.cpp",
+ "fuzz_target_runner.cpp",
+ "java_reproducer.cpp",
+ "java_reproducer.h",
+ "java_reproducer_templates.h",
+ "jvm_tooling.cpp",
+ "libfuzzer_callbacks.cpp",
+ "libfuzzer_callbacks.h",
+ "libfuzzer_driver.cpp",
+ "signal_handler.cpp",
+ "signal_handler.h",
+ "utils.cpp",
+ "utils.h",
+ ],
+ hdrs = [
+ "coverage_tracker.h",
+ "fuzz_target_runner.h",
+ "fuzzed_data_provider.h",
+ "jvm_tooling.h",
+ "libfuzzer_driver.h",
+ ],
+ linkopts = select({
+ "@platforms//os:windows": [],
+ "//conditions:default": ["-ldl"],
+ }),
+ # Needs to be linked statically for JNI_OnLoad_jazzer_initialize to be found
+ # by the JVM.
+ linkstatic = True,
+ local_defines = select({
+ # Windows does not have SIGUSR1, which triggers a graceful exit of
+ # libFuzzer. Instead, trigger a hard exit.
+ "@platforms//os:windows": ["SIGUSR1=SIGTERM"],
+ "//conditions:default": [],
+ }),
+ tags = [
+ # Should be built through the cc_17_library driver_lib.
+ "manual",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":fuzzed_data_provider",
+ ":sanitizer_hooks_with_pc",
+ "@bazel_tools//tools/cpp/runfiles",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/strings:str_format",
+ "@com_google_glog//:glog",
+ "@fmeum_rules_jni//jni:libjvm",
+ "@jazzer_com_github_gflags_gflags//:gflags",
+ ],
+)
+
+cc_17_library(
+ name = "driver_lib",
+ srcs = [
+ "libfuzzer_fuzz_target.cpp",
+ ],
+ linkstatic = True,
+ deps = [
+ ":jvm_tooling_lib",
+ "@jazzer_libfuzzer//:libFuzzer",
+ ],
+ alwayslink = True,
+)
+
+cc_binary(
+ name = "jazzer_driver",
+ srcs = [
+ # Defines symbols otherwise defined by sanitizers to prevent linker
+ # errors and print JVM stack traces.
+ # Windows-compatible replacement for __attribute__((weak)).
+ "sanitizer_symbols.cpp",
+ ],
+ data = [
+ "//agent:jazzer_agent_deploy.jar",
+ ],
+ linkopts = select({
+ "@platforms//os:windows": [],
+ "//conditions:default": [
+ "-rdynamic",
+ ],
+ }) + select({
+ "//:clang_on_linux": ["-fuse-ld=lld"],
+ "//conditions:default": [],
+ }),
+ linkstatic = True,
+ visibility = ["//visibility:public"],
+ deps = [":driver_lib"],
+)
+
+alias(
+ name = "using_toolchain_on_osx",
+ actual = select({
+ "//third_party:uses_toolchain": "@platforms//os:osx",
+ # In order to achieve AND semantics, reference a setting that is known
+ # not to apply.
+ "//conditions:default": "//third_party:uses_toolchain",
+ }),
+)
+
+cc_binary(
+ name = "jazzer_driver_asan",
+ data = [
+ "//agent:jazzer_agent_deploy.jar",
+ ],
+ linkopts = [
+ ] + select({
+ "@platforms//os:windows": [
+ # Sanitizer runtimes have to be linked manually on Windows:
+ # https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/
+ "/wholearchive:clang_rt.asan-x86_64.lib",
+ "/wholearchive:clang_rt.asan_cxx-x86_64.lib",
+ ],
+ "//conditions:default": [
+ "-fsanitize=address",
+ "-static-libsan",
+ "-rdynamic",
+ ],
+ }) + select({
+ "//:clang_on_linux": ["-fuse-ld=lld"],
+ "//conditions:default": [],
+ }),
+ linkstatic = True,
+ visibility = ["//visibility:public"],
+ deps = [":driver_lib"] + select({
+ # There is no static ASan runtime on macOS, so link to the dynamic
+ # runtime library if on macOS and using the toolchain.
+ ":using_toolchain_on_osx": ["@llvm_toolchain_llvm//:macos_asan_dynamic"],
+ "//conditions:default": [],
+ }),
+)
+
+cc_binary(
+ name = "jazzer_driver_ubsan",
+ data = [
+ "//agent:jazzer_agent_deploy.jar",
+ ],
+ linkopts = [
+ ] + select({
+ "@platforms//os:windows": [
+ # Sanitizer runtimes have to be linked manually on Windows:
+ # https://devblogs.microsoft.com/cppblog/addresssanitizer-asan-for-windows-with-msvc/
+ "/wholearchive:clang_rt.ubsan_standalone-x86_64.lib",
+ "/wholearchive:clang_rt.ubsan_standalone_cxx-x86_64.lib",
+ ],
+ "//conditions:default": [
+ "-fsanitize=undefined",
+ # Link UBSan statically, even on macOS.
+ "-static-libsan",
+ "-fsanitize-link-c++-runtime",
+ "-rdynamic",
+ ],
+ }) + select({
+ "//:clang_on_linux": ["-fuse-ld=lld"],
+ "//conditions:default": [],
+ }),
+ linkstatic = True,
+ visibility = ["//visibility:public"],
+ deps = [":driver_lib"],
+)
+
+cc_test(
+ name = "jvm_tooling_test",
+ size = "small",
+ srcs = [
+ "jvm_tooling_test.cpp",
+ "sanitizer_symbols_for_tests.cpp",
+ ],
+ args = [
+ "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)",
+ ],
+ data = [
+ "//agent:jazzer_agent_deploy.jar",
+ "//driver/testdata:fuzz_target_mocks_deploy.jar",
+ ],
+ includes = ["."],
+ linkopts = select({
+ "@platforms//os:windows": [],
+ "//conditions:default": [
+ # Needs to export symbols dynamically for JNI_OnLoad_jazzer_initialize
+ # to be found by the JVM.
+ "-rdynamic",
+ ],
+ }),
+ deps = [
+ ":jvm_tooling_lib",
+ ":test_main",
+ "@bazel_tools//tools/cpp/runfiles",
+ "@googletest//:gtest",
+ "@jazzer_com_github_gflags_gflags//:gflags",
+ ],
+)
+
+cc_test(
+ name = "fuzzed_data_provider_test",
+ size = "medium",
+ srcs = [
+ "fuzzed_data_provider_test.cpp",
+ "sanitizer_symbols_for_tests.cpp",
+ ],
+ args = [
+ "--cp=jazzer/$(rootpath //driver/testdata:fuzz_target_mocks_deploy.jar)",
+ ],
+ data = [
+ "//agent:jazzer_agent_deploy.jar",
+ "//driver/testdata:fuzz_target_mocks_deploy.jar",
+ ],
+ includes = ["."],
+ deps = [
+ ":jvm_tooling_lib",
+ ":test_main",
+ "@bazel_tools//tools/cpp/runfiles",
+ "@googletest//:gtest",
+ "@jazzer_com_github_gflags_gflags//:gflags",
+ ],
+)
+
+cc_library(
+ name = "test_main",
+ srcs = ["test_main.cpp"],
+ linkstatic = True,
+ deps = [
+ "@googletest//:gtest",
+ "@jazzer_com_github_gflags_gflags//:gflags",
+ ],
+)
diff --git a/driver/coverage_tracker.cpp b/driver/coverage_tracker.cpp
new file mode 100644
index 00000000..0a576085
--- /dev/null
+++ b/driver/coverage_tracker.cpp
@@ -0,0 +1,215 @@
+// Copyright 2021 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.
+
+#include "coverage_tracker.h"
+
+#include <jni.h>
+
+#include <algorithm>
+#include <memory>
+#include <stdexcept>
+
+#include "absl/strings/str_format.h"
+
+extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start,
+ uint8_t *end);
+extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
+ const uintptr_t *pcs_end);
+extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries);
+
+constexpr auto kCoverageMapClass =
+ "com/code_intelligence/jazzer/runtime/CoverageMap";
+constexpr auto kByteBufferClass = "java/nio/ByteBuffer";
+constexpr auto kCoverageRecorderClass =
+ "com/code_intelligence/jazzer/instrumentor/CoverageRecorder";
+
+// The initial size of the Java coverage map (512 counters).
+constexpr std::size_t kInitialCoverageCountersBufferSize = 1u << 9u;
+// The maximum size of the Java coverage map (1,048,576 counters).
+// Since the memory for the coverage map needs to be allocated contiguously,
+// increasing the maximum size incurs additional memory (but not runtime)
+// overhead for all fuzz targets.
+constexpr std::size_t kMaxCoverageCountersBufferSize = 1u << 20u;
+static_assert(kMaxCoverageCountersBufferSize <=
+ std::numeric_limits<jint>::max());
+
+namespace {
+void AssertNoException(JNIEnv &env) {
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(
+ "Java exception occurred in CoverageTracker JNI code");
+ }
+}
+} // namespace
+
+namespace jazzer {
+
+uint8_t *CoverageTracker::counters_ = nullptr;
+uint32_t *CoverageTracker::fake_instructions_ = nullptr;
+PCTableEntry *CoverageTracker::pc_entries_ = nullptr;
+
+void CoverageTracker::Setup(JNIEnv &env) {
+ if (counters_ != nullptr) {
+ throw std::runtime_error(
+ "CoverageTracker::Setup must not be called more than once");
+ }
+ JNINativeMethod coverage_tracker_native_methods[]{
+ {(char *)"registerNewCoverageCounters", (char *)"()V",
+ (void *)&RegisterNewCoverageCounters},
+ };
+ jclass coverage_map = env.FindClass(kCoverageMapClass);
+ env.RegisterNatives(coverage_map, coverage_tracker_native_methods, 1);
+
+ // libFuzzer requires an array containing the instruction addresses associated
+ // with the coverage counters registered above. Given that we are
+ // instrumenting Java code, we need to synthesize addresses that are known not
+ // to conflict with any valid instruction address in native code. Just like
+ // atheris we ensure there are no collisions by using the addresses of an
+ // allocated buffer. Note: We intentionally never deallocate the allocations
+ // made here as they have static lifetime and we can't guarantee they wouldn't
+ // be freed before libFuzzer stops using them.
+ constexpr std::size_t counters_size = kMaxCoverageCountersBufferSize;
+ counters_ = new uint8_t[counters_size];
+ Clear();
+
+ // Never deallocated, see above.
+ fake_instructions_ = new uint32_t[counters_size];
+ std::fill(fake_instructions_, fake_instructions_ + counters_size, 0);
+
+ // Never deallocated, see above.
+ pc_entries_ = new PCTableEntry[counters_size];
+ for (std::size_t i = 0; i < counters_size; ++i) {
+ pc_entries_[i].PC = reinterpret_cast<uintptr_t>(fake_instructions_ + i);
+ // TODO: Label Java PCs corresponding to functions as such.
+ pc_entries_[i].PCFlags = 0;
+ }
+
+ // Register the first batch of coverage counters.
+ RegisterNewCoverageCounters(env, nullptr);
+}
+
+void JNICALL CoverageTracker::RegisterNewCoverageCounters(JNIEnv &env,
+ jclass cls) {
+ jclass coverage_map = env.FindClass(kCoverageMapClass);
+ AssertNoException(env);
+ jfieldID counters_buffer_id = env.GetStaticFieldID(
+ coverage_map, "mem", absl::StrFormat("L%s;", kByteBufferClass).c_str());
+ AssertNoException(env);
+ jobject counters_buffer =
+ env.GetStaticObjectField(coverage_map, counters_buffer_id);
+ AssertNoException(env);
+
+ jclass byte_buffer = env.FindClass(kByteBufferClass);
+ AssertNoException(env);
+ jmethodID byte_buffer_capacity_id =
+ env.GetMethodID(byte_buffer, "capacity", "()I");
+ AssertNoException(env);
+ jint old_counters_buffer_size =
+ env.CallIntMethod(counters_buffer, byte_buffer_capacity_id);
+ AssertNoException(env);
+
+ jint new_counters_buffer_size;
+ if (old_counters_buffer_size == 0) {
+ new_counters_buffer_size = kInitialCoverageCountersBufferSize;
+ } else {
+ new_counters_buffer_size = 2 * old_counters_buffer_size;
+ if (new_counters_buffer_size > kMaxCoverageCountersBufferSize) {
+ throw std::runtime_error(
+ "Maximal size of the coverage counters buffer exceeded");
+ }
+ }
+
+ jobject new_counters_buffer = env.NewDirectByteBuffer(
+ static_cast<void *>(counters_), new_counters_buffer_size);
+ AssertNoException(env);
+ env.SetStaticObjectField(coverage_map, counters_buffer_id,
+ new_counters_buffer);
+ AssertNoException(env);
+
+ // Register only the new second half of the counters buffer with libFuzzer.
+ __sanitizer_cov_8bit_counters_init(counters_ + old_counters_buffer_size,
+ counters_ + new_counters_buffer_size);
+ __sanitizer_cov_pcs_init(
+ (uintptr_t *)(pc_entries_ + old_counters_buffer_size),
+ (uintptr_t *)(pc_entries_ + new_counters_buffer_size));
+}
+
+void CoverageTracker::Clear() {
+ std::fill(counters_, counters_ + kMaxCoverageCountersBufferSize, 0);
+}
+
+uint8_t *CoverageTracker::GetCoverageCounters() { return counters_; }
+
+void CoverageTracker::RecordInitialCoverage(JNIEnv &env) {
+ jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
+ AssertNoException(env);
+ jmethodID coverage_recorder_update_covered_ids_with_coverage_map =
+ env.GetStaticMethodID(coverage_recorder,
+ "updateCoveredIdsWithCoverageMap", "()V");
+ AssertNoException(env);
+ env.CallStaticVoidMethod(
+ coverage_recorder,
+ coverage_recorder_update_covered_ids_with_coverage_map);
+ AssertNoException(env);
+}
+
+void CoverageTracker::ReplayInitialCoverage(JNIEnv &env) {
+ jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
+ AssertNoException(env);
+ jmethodID coverage_recorder_update_covered_ids_with_coverage_map =
+ env.GetStaticMethodID(coverage_recorder, "replayCoveredIds", "()V");
+ AssertNoException(env);
+ env.CallStaticVoidMethod(
+ coverage_recorder,
+ coverage_recorder_update_covered_ids_with_coverage_map);
+ AssertNoException(env);
+}
+
+std::string CoverageTracker::ComputeCoverage(JNIEnv &env) {
+ uintptr_t *covered_pcs;
+ size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs);
+ std::vector<jint> covered_edge_ids{};
+ covered_edge_ids.reserve(num_covered_pcs);
+ const uintptr_t first_pc = pc_entries_[0].PC;
+ std::for_each(covered_pcs, covered_pcs + num_covered_pcs,
+ [&covered_edge_ids, first_pc](const uintptr_t pc) {
+ jint edge_id =
+ (pc - first_pc) / sizeof(fake_instructions_[0]);
+ covered_edge_ids.push_back(edge_id);
+ });
+ delete[] covered_pcs;
+
+ jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
+ AssertNoException(env);
+ jmethodID coverage_recorder_compute_file_coverage = env.GetStaticMethodID(
+ coverage_recorder, "computeFileCoverage", "([I)Ljava/lang/String;");
+ AssertNoException(env);
+ jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs);
+ AssertNoException(env);
+ env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs,
+ covered_edge_ids.data());
+ AssertNoException(env);
+ auto file_coverage_jni = (jstring)(env.CallStaticObjectMethod(
+ coverage_recorder, coverage_recorder_compute_file_coverage,
+ covered_edge_ids_jni));
+ AssertNoException(env);
+ auto file_coverage_cstr = env.GetStringUTFChars(file_coverage_jni, nullptr);
+ AssertNoException(env);
+ std::string file_coverage(file_coverage_cstr);
+ env.ReleaseStringUTFChars(file_coverage_jni, file_coverage_cstr);
+ AssertNoException(env);
+ return file_coverage;
+}
+} // namespace jazzer
diff --git a/driver/coverage_tracker.h b/driver/coverage_tracker.h
new file mode 100644
index 00000000..5b237de3
--- /dev/null
+++ b/driver/coverage_tracker.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <string>
+
+#include "jvm_tooling.h"
+
+namespace jazzer {
+
+// The members of this struct are only accessed by libFuzzer.
+struct __attribute__((packed)) PCTableEntry {
+ [[maybe_unused]] uintptr_t PC, PCFlags;
+};
+
+// CoverageTracker registers an array of 8-bit coverage counters with
+// libFuzzer. The array is backed by a MappedByteBuffer on the Java
+// side, where it is populated with the actual coverage information.
+class CoverageTracker : public ExceptionPrinter {
+ private:
+ static uint8_t *counters_;
+
+ static uint32_t *fake_instructions_;
+ static PCTableEntry *pc_entries_;
+
+ static void JNICALL RegisterNewCoverageCounters(JNIEnv &env, jclass cls);
+
+ public:
+ static void Setup(JNIEnv &env);
+ // Clears the coverage counters array manually. It is cleared automatically
+ // by libFuzzer prior to running the fuzz target, so this function is only
+ // used in tests.
+ static void Clear();
+
+ // Returns the address of the coverage counters array.
+ static uint8_t *GetCoverageCounters();
+
+ static void RecordInitialCoverage(JNIEnv &env);
+ static void ReplayInitialCoverage(JNIEnv &env);
+ static std::string ComputeCoverage(JNIEnv &env);
+};
+} // namespace jazzer
diff --git a/driver/fuzz_target_runner.cpp b/driver/fuzz_target_runner.cpp
new file mode 100644
index 00000000..934e27e1
--- /dev/null
+++ b/driver/fuzz_target_runner.cpp
@@ -0,0 +1,398 @@
+// Copyright 2021 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.
+
+#include "fuzz_target_runner.h"
+
+#include <jni.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/substitute.h"
+#include "coverage_tracker.h"
+#include "fuzzed_data_provider.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "java_reproducer.h"
+#include "java_reproducer_templates.h"
+#include "utils.h"
+
+DEFINE_string(
+ target_class, "",
+ "The Java class that contains the static fuzzerTestOneInput function");
+DEFINE_string(target_args, "",
+ "Arguments passed to fuzzerInitialize as a String array. "
+ "Separated by space.");
+
+DEFINE_uint32(keep_going, 0,
+ "Continue fuzzing until N distinct exception stack traces have"
+ "been encountered. Defaults to exit after the first finding "
+ "unless --autofuzz is specified.");
+DEFINE_bool(dedup, true,
+ "Emit a dedup token for every finding. Defaults to true and is "
+ "required for --keep_going and --ignore.");
+DEFINE_string(
+ ignore, "",
+ "Comma-separated list of crash dedup tokens to ignore. This is useful to "
+ "continue fuzzing before a crash is fixed.");
+
+DEFINE_string(reproducer_path, ".",
+ "Path at which fuzzing reproducers are stored. Defaults to the "
+ "current directory.");
+DEFINE_string(coverage_report, "",
+ "Path at which a coverage report is stored when the fuzzer "
+ "exits. If left empty, no report is generated (default)");
+
+DEFINE_string(autofuzz, "",
+ "Fully qualified reference to a method on the classpath that "
+ "should be fuzzed automatically (example: System.out::println). "
+ "Fuzzing will continue even after a finding; specify "
+ "--keep_going=N to stop after N findings.");
+DEFINE_string(autofuzz_ignore, "",
+ "Fully qualified class names of exceptions to ignore during "
+ "autofuzz. Separated by comma.");
+
+DECLARE_bool(hooks);
+
+constexpr auto kManifestUtilsClass =
+ "com/code_intelligence/jazzer/runtime/ManifestUtils";
+constexpr auto kJazzerClass =
+ "com/code_intelligence/jazzer/runtime/JazzerInternal";
+constexpr auto kAutofuzzFuzzTargetClass =
+ "com/code_intelligence/jazzer/autofuzz/FuzzTarget";
+
+namespace jazzer {
+// split a string on unescaped spaces
+std::vector<std::string> splitOnSpace(const std::string &s) {
+ if (s.empty()) {
+ return {};
+ }
+
+ std::vector<std::string> tokens;
+ std::size_t token_begin = 0;
+ for (std::size_t i = 1; i < s.size() - 1; i++) {
+ // only split if the space is not escaped by a backslash "\"
+ if (s[i] == ' ' && s[i - 1] != '\\') {
+ // don't split on multiple spaces
+ if (i > token_begin + 1)
+ tokens.push_back(s.substr(token_begin, i - token_begin));
+ token_begin = i + 1;
+ }
+ }
+ tokens.push_back(s.substr(token_begin));
+ return tokens;
+}
+
+FuzzTargetRunner::FuzzTargetRunner(
+ JVM &jvm, const std::vector<std::string> &additional_target_args)
+ : ExceptionPrinter(jvm), jvm_(jvm), ignore_tokens_() {
+ auto &env = jvm.GetEnv();
+ if (!FLAGS_target_class.empty() && !FLAGS_autofuzz.empty()) {
+ std::cerr << "--target_class and --autofuzz cannot be specified together"
+ << std::endl;
+ exit(1);
+ }
+ if (!FLAGS_target_args.empty() && !FLAGS_autofuzz.empty()) {
+ std::cerr << "--target_args and --autofuzz cannot be specified together"
+ << std::endl;
+ exit(1);
+ }
+ if (FLAGS_autofuzz.empty() && !FLAGS_autofuzz_ignore.empty()) {
+ std::cerr << "--autofuzz_ignore requires --autofuzz" << std::endl;
+ exit(1);
+ }
+ if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
+ FLAGS_target_class = DetectFuzzTargetClass();
+ }
+ // If automatically detecting the fuzz target class failed, we expect it as
+ // the value of the --target_class argument.
+ if (FLAGS_target_class.empty() && FLAGS_autofuzz.empty()) {
+ std::cerr << "Missing argument --target_class=<fuzz_target_class>"
+ << std::endl;
+ exit(1);
+ }
+ if (!FLAGS_autofuzz.empty()) {
+ FLAGS_target_class = kAutofuzzFuzzTargetClass;
+ if (FLAGS_keep_going == 0) {
+ FLAGS_keep_going = std::numeric_limits<gflags::uint32>::max();
+ }
+ // Pass the method reference string as the first argument to the generic
+ // autofuzz fuzz target. Subseqeuent arguments are interpreted as exception
+ // class names that should be ignored.
+ FLAGS_target_args = FLAGS_autofuzz;
+ if (!FLAGS_autofuzz_ignore.empty()) {
+ FLAGS_target_args = absl::StrCat(
+ FLAGS_target_args, " ",
+ absl::StrReplaceAll(FLAGS_autofuzz_ignore, {{",", " "}}));
+ }
+ }
+ // Set --keep_going to its real default.
+ if (FLAGS_keep_going == 0) {
+ FLAGS_keep_going = 1;
+ }
+ if ((!FLAGS_ignore.empty() || FLAGS_keep_going > 1) && !FLAGS_dedup) {
+ std::cerr << "--nodedup is not supported with --ignore or --keep_going"
+ << std::endl;
+ exit(1);
+ }
+ jazzer_ = jvm.FindClass(kJazzerClass);
+ last_finding_ =
+ env.GetStaticFieldID(jazzer_, "lastFinding", "Ljava/lang/Throwable;");
+
+ jclass_ = jvm.FindClass(FLAGS_target_class);
+ // one of the following functions is required:
+ // public static void fuzzerTestOneInput(byte[] input)
+ // public static void fuzzerTestOneInput(FuzzedDataProvider data)
+ fuzzer_test_one_input_bytes_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerTestOneInput", "([B)V", false);
+ fuzzer_test_one_input_data_ = jvm.GetStaticMethodID(
+ jclass_, "fuzzerTestOneInput",
+ "(Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;)V", false);
+ bool using_bytes = fuzzer_test_one_input_bytes_ != nullptr;
+ bool using_data = fuzzer_test_one_input_data_ != nullptr;
+ // Fail if none ore both of the two possible fuzzerTestOneInput versions is
+ // defined in the class.
+ if (using_bytes == using_data) {
+ LOG(ERROR) << FLAGS_target_class
+ << " must define exactly one of the following two functions:";
+ LOG(ERROR) << "public static void fuzzerTestOneInput(byte[] ...)";
+ LOG(ERROR)
+ << "public static void fuzzerTestOneInput(FuzzedDataProvider ...)";
+ LOG(ERROR) << "Note: Fuzz targets returning boolean are no longer "
+ "supported; exceptions should be thrown instead of "
+ "returning true.";
+ exit(1);
+ }
+
+ // check existence of optional methods for initialization and destruction
+ fuzzer_initialize_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerInitialize", "()V", false);
+ fuzzer_tear_down_ =
+ jvm.GetStaticMethodID(jclass_, "fuzzerTearDown", "()V", false);
+ fuzzer_initialize_with_args_ = jvm.GetStaticMethodID(
+ jclass_, "fuzzerInitialize", "([Ljava/lang/String;)V", false);
+
+ auto fuzz_target_args_tokens = splitOnSpace(FLAGS_target_args);
+ fuzz_target_args_tokens.insert(fuzz_target_args_tokens.end(),
+ additional_target_args.begin(),
+ additional_target_args.end());
+
+ if (fuzzer_initialize_with_args_) {
+ // fuzzerInitialize with arguments gets priority
+ jclass string_class = jvm.FindClass("java/lang/String");
+ jobjectArray arg_array = jvm.GetEnv().NewObjectArray(
+ fuzz_target_args_tokens.size(), string_class, nullptr);
+ for (jint i = 0; i < fuzz_target_args_tokens.size(); i++) {
+ jstring str = env.NewStringUTF(fuzz_target_args_tokens[i].c_str());
+ env.SetObjectArrayElement(arg_array, i, str);
+ }
+ env.CallStaticObjectMethod(jclass_, fuzzer_initialize_with_args_,
+ arg_array);
+ } else if (fuzzer_initialize_) {
+ env.CallStaticVoidMethod(jclass_, fuzzer_initialize_);
+ } else {
+ LOG(INFO) << "did not call any fuzz target initialize functions";
+ }
+
+ if (jthrowable exception = env.ExceptionOccurred()) {
+ LOG(ERROR) << "== Java Exception in fuzzerInitialize: ";
+ LOG(ERROR) << getStackTrace(exception);
+ std::exit(1);
+ }
+
+ if (FLAGS_hooks) {
+ CoverageTracker::RecordInitialCoverage(env);
+ }
+ SetUpFuzzedDataProvider(jvm_.GetEnv());
+
+ // Parse a comma-separated list of hex dedup tokens.
+ std::vector<std::string> str_ignore_tokens =
+ absl::StrSplit(FLAGS_ignore, ',');
+ for (const std::string &str_token : str_ignore_tokens) {
+ if (str_token.empty()) continue;
+ try {
+ ignore_tokens_.push_back(std::stoull(str_token, nullptr, 16));
+ } catch (...) {
+ LOG(ERROR) << "Invalid dedup token (expected up to 16 hex digits): '"
+ << str_token << "'";
+ // Don't let libFuzzer print a crash stack trace.
+ _Exit(1);
+ }
+ }
+}
+
+FuzzTargetRunner::~FuzzTargetRunner() {
+ if (FLAGS_hooks && !FLAGS_coverage_report.empty()) {
+ std::string report = CoverageTracker::ComputeCoverage(jvm_.GetEnv());
+ std::ofstream report_file(FLAGS_coverage_report);
+ if (report_file) {
+ report_file << report << std::flush;
+ } else {
+ LOG(ERROR) << "Failed to write coverage report to "
+ << FLAGS_coverage_report;
+ }
+ }
+ if (fuzzer_tear_down_ != nullptr) {
+ std::cerr << "calling fuzzer teardown function" << std::endl;
+ jvm_.GetEnv().CallStaticVoidMethod(jclass_, fuzzer_tear_down_);
+ if (jthrowable exception = jvm_.GetEnv().ExceptionOccurred())
+ std::cerr << getStackTrace(exception) << std::endl;
+ }
+}
+
+RunResult FuzzTargetRunner::Run(const uint8_t *data, const std::size_t size) {
+ auto &env = jvm_.GetEnv();
+ static std::size_t run_count = 0;
+ if (run_count < 2) {
+ run_count++;
+ // For the first two runs only, replay the coverage recorded from static
+ // initializers. libFuzzer cleared the coverage map after they ran and could
+ // fail to see any coverage, triggering an early exit, if we don't replay it
+ // here.
+ // https://github.com/llvm/llvm-project/blob/957a5e987444d3193575d6ad8afe6c75da00d794/compiler-rt/lib/fuzzer/FuzzerLoop.cpp#L804-L809
+ CoverageTracker::ReplayInitialCoverage(env);
+ }
+ if (fuzzer_test_one_input_data_ != nullptr) {
+ FeedFuzzedDataProvider(data, size);
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_,
+ GetFuzzedDataProviderJavaObject(jvm_));
+ } else {
+ jbyteArray byte_array = env.NewByteArray(size);
+ if (byte_array == nullptr) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(std::string("Cannot create byte array"));
+ }
+ env.SetByteArrayRegion(byte_array, 0, size,
+ reinterpret_cast<const jbyte *>(data));
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_bytes_, byte_array);
+ env.DeleteLocalRef(byte_array);
+ }
+
+ const auto finding = GetFinding();
+ if (finding != nullptr) {
+ jlong dedup_token = computeDedupToken(finding);
+ // Check whether this stack trace has been encountered before if
+ // `--keep_going` has been supplied.
+ if (dedup_token != 0 && FLAGS_keep_going > 1 &&
+ std::find(ignore_tokens_.cbegin(), ignore_tokens_.cend(),
+ dedup_token) != ignore_tokens_.end()) {
+ env.DeleteLocalRef(finding);
+ return RunResult::kOk;
+ } else {
+ ignore_tokens_.push_back(dedup_token);
+ std::cout << std::endl;
+ std::cerr << "== Java Exception: " << getStackTrace(finding);
+ env.DeleteLocalRef(finding);
+ if (FLAGS_dedup) {
+ std::cout << "DEDUP_TOKEN: " << std::hex << std::setfill('0')
+ << std::setw(16) << dedup_token << std::endl;
+ }
+ if (ignore_tokens_.size() < static_cast<std::size_t>(FLAGS_keep_going)) {
+ return RunResult::kDumpAndContinue;
+ } else {
+ return RunResult::kException;
+ }
+ }
+ }
+ return RunResult::kOk;
+}
+
+// Returns a fuzzer finding as a Throwable (or nullptr if there is none),
+// clearing any JVM exceptions in the process.
+jthrowable FuzzTargetRunner::GetFinding() const {
+ auto &env = jvm_.GetEnv();
+ jthrowable unprocessed_finding = nullptr;
+ if (env.ExceptionCheck()) {
+ unprocessed_finding = env.ExceptionOccurred();
+ env.ExceptionClear();
+ }
+ // Explicitly reported findings take precedence over uncaught exceptions.
+ if (auto reported_finding =
+ (jthrowable)env.GetStaticObjectField(jazzer_, last_finding_);
+ reported_finding != nullptr) {
+ env.DeleteLocalRef(unprocessed_finding);
+ unprocessed_finding = reported_finding;
+ }
+ jthrowable processed_finding = preprocessException(unprocessed_finding);
+ env.DeleteLocalRef(unprocessed_finding);
+ return processed_finding;
+}
+
+void FuzzTargetRunner::DumpReproducer(const uint8_t *data, std::size_t size) {
+ auto &env = jvm_.GetEnv();
+ std::string base64_data;
+ if (fuzzer_test_one_input_data_) {
+ // Record the data retrieved from the FuzzedDataProvider and supply it to a
+ // Java-only CannedFuzzedDataProvider in the reproducer.
+ FeedFuzzedDataProvider(data, size);
+ jobject recorder = GetRecordingFuzzedDataProviderJavaObject(jvm_);
+ env.CallStaticVoidMethod(jclass_, fuzzer_test_one_input_data_, recorder);
+ const auto finding = GetFinding();
+ if (finding == nullptr) {
+ LOG(ERROR) << "Failed to reproduce crash when rerunning with recorder";
+ return;
+ }
+ base64_data = SerializeRecordingFuzzedDataProvider(jvm_, recorder);
+ } else {
+ absl::string_view data_str(reinterpret_cast<const char *>(data), size);
+ absl::Base64Escape(data_str, &base64_data);
+ }
+ const char *fuzz_target_call = fuzzer_test_one_input_data_
+ ? kTestOneInputWithData
+ : kTestOneInputWithBytes;
+ std::string data_sha1 = jazzer::Sha1Hash(data, size);
+ std::string reproducer =
+ absl::Substitute(kBaseReproducer, data_sha1, base64_data,
+ FLAGS_target_class, fuzz_target_call);
+ std::string reproducer_filename = absl::StrFormat("Crash_%s.java", data_sha1);
+ std::string reproducer_full_path = absl::StrFormat(
+ "%s%c%s", FLAGS_reproducer_path, kPathSeparator, reproducer_filename);
+ std::ofstream reproducer_out(reproducer_full_path);
+ reproducer_out << reproducer;
+ std::cout << absl::StrFormat(
+ "reproducer_path='%s'; Java reproducer written to %s",
+ FLAGS_reproducer_path, reproducer_full_path)
+ << std::endl;
+}
+
+std::string FuzzTargetRunner::DetectFuzzTargetClass() const {
+ jclass manifest_utils = jvm_.FindClass(kManifestUtilsClass);
+ jmethodID detect_fuzz_target_class = jvm_.GetStaticMethodID(
+ manifest_utils, "detectFuzzTargetClass", "()Ljava/lang/String;", true);
+ auto &env = jvm_.GetEnv();
+ auto jni_fuzz_target_class = (jstring)(env.CallStaticObjectMethod(
+ manifest_utils, detect_fuzz_target_class));
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ if (jni_fuzz_target_class == nullptr) return "";
+
+ const char *fuzz_target_class_cstr =
+ env.GetStringUTFChars(jni_fuzz_target_class, nullptr);
+ std::string fuzz_target_class = std::string(fuzz_target_class_cstr);
+ env.ReleaseStringUTFChars(jni_fuzz_target_class, fuzz_target_class_cstr);
+ env.DeleteLocalRef(jni_fuzz_target_class);
+
+ return fuzz_target_class;
+}
+} // namespace jazzer
diff --git a/driver/fuzz_target_runner.h b/driver/fuzz_target_runner.h
new file mode 100644
index 00000000..98ac794c
--- /dev/null
+++ b/driver/fuzz_target_runner.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <string>
+#include <vector>
+
+#include "jvm_tooling.h"
+
+namespace jazzer {
+
+enum class RunResult {
+ kOk,
+ kException,
+ kDumpAndContinue,
+};
+
+// Invokes the following static methods in the java fuzz target class:
+// 1. On construction:
+// - `public static void fuzzerInitialize()`
+// OR
+// - `public static void fuzzerInitialize(String[] args)`
+// 2. On every call of Run():
+// - `public static void fuzzerTestOneInput(FuzzedDataProvider data)`
+// OR
+// - `public static void fuzzerTestOneInput(byte[] input)`
+// 3. On destruction:
+// - `public static void fuzzerTearDown()`
+class FuzzTargetRunner : public ExceptionPrinter {
+ private:
+ const JVM &jvm_;
+ jclass jclass_;
+ jmethodID fuzzer_initialize_;
+ jmethodID fuzzer_initialize_with_args_;
+ jmethodID fuzzer_test_one_input_bytes_;
+ jmethodID fuzzer_test_one_input_data_;
+ jmethodID fuzzer_tear_down_;
+ jclass jazzer_;
+ jfieldID last_finding_;
+ std::vector<jlong> ignore_tokens_;
+
+ [[nodiscard]] std::string DetectFuzzTargetClass() const;
+ [[nodiscard]] jthrowable GetFinding() const;
+
+ public:
+ // Initializes the java fuzz target by calling `void fuzzerInitialize(...)`.
+ explicit FuzzTargetRunner(
+ JVM &jvm, const std::vector<std::string> &additional_target_args = {});
+
+ // Calls the fuzz target tear down function. This can be useful to join any
+ // Threads so that the JVM shuts down correctly.
+ virtual ~FuzzTargetRunner();
+
+ // Propagate the fuzzer input to the java fuzz target.
+ RunResult Run(const uint8_t *data, std::size_t size);
+
+ void DumpReproducer(const uint8_t *data, std::size_t size);
+};
+
+} // namespace jazzer
diff --git a/driver/fuzzed_data_provider.cpp b/driver/fuzzed_data_provider.cpp
new file mode 100644
index 00000000..e8cb971b
--- /dev/null
+++ b/driver/fuzzed_data_provider.cpp
@@ -0,0 +1,719 @@
+// Copyright 2021 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.
+//
+// Modified from
+// https://raw.githubusercontent.com/google/atheris/034284dc4bb1ad4f4ab6ba5d34fb4dca7c633660/fuzzed_data_provider.cc
+//
+// Original license and copyright notices:
+//
+// Copyright 2020 Google LLC
+//
+// 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.
+//
+// Modified from
+// https://github.com/llvm/llvm-project/blob/70de7e0d9a95b7fcd7c105b06bd90fdf4e01f563/compiler-rt/include/fuzzer/FuzzedDataProvider.h
+//
+// Original license and copyright notices:
+//
+//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+
+#include "fuzzed_data_provider.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "absl/strings/str_format.h"
+
+namespace {
+
+const uint8_t *gDataPtr = nullptr;
+std::size_t gRemainingBytes = 0;
+
+// Advance by `bytes` bytes in the buffer or stay at the end if it has been
+// consumed.
+void Advance(const std::size_t bytes) {
+ if (bytes > gRemainingBytes) {
+ gRemainingBytes = 0;
+ } else {
+ gDataPtr += bytes;
+ gRemainingBytes -= bytes;
+ }
+}
+
+void ThrowIllegalArgumentException(JNIEnv &env, const std::string &message) {
+ jclass illegal_argument_exception =
+ env.FindClass("java/lang/IllegalArgumentException");
+ env.ThrowNew(illegal_argument_exception, message.c_str());
+}
+
+template <typename T>
+struct JniArrayType {};
+
+#define JNI_ARRAY_TYPE(lower_case, sentence_case) \
+ template <> \
+ struct JniArrayType<j##lower_case> { \
+ typedef j##lower_case type; \
+ typedef j##lower_case##Array array_type; \
+ static constexpr array_type (JNIEnv::*kNewArrayFunc)(jsize) = \
+ &JNIEnv::New##sentence_case##Array; \
+ static constexpr void (JNIEnv::*kSetArrayRegionFunc)( \
+ array_type array, jsize start, jsize len, \
+ const type *buf) = &JNIEnv::Set##sentence_case##ArrayRegion; \
+ };
+
+JNI_ARRAY_TYPE(boolean, Boolean);
+JNI_ARRAY_TYPE(byte, Byte);
+JNI_ARRAY_TYPE(short, Short);
+JNI_ARRAY_TYPE(int, Int);
+JNI_ARRAY_TYPE(long, Long);
+
+template <typename T>
+typename JniArrayType<T>::array_type JNICALL
+ConsumeIntegralArray(JNIEnv &env, jobject self, jint max_length) {
+ if (max_length < 0) {
+ ThrowIllegalArgumentException(env, "maxLength must not be negative");
+ return nullptr;
+ }
+ // Arrays of integral types are considered data and thus consumed from the
+ // beginning of the buffer.
+ std::size_t max_num_bytes = std::min(sizeof(T) * max_length, gRemainingBytes);
+ jsize actual_length = max_num_bytes / sizeof(T);
+ std::size_t actual_num_bytes = sizeof(T) * actual_length;
+ auto array = (env.*(JniArrayType<T>::kNewArrayFunc))(actual_length);
+ (env.*(JniArrayType<T>::kSetArrayRegionFunc))(
+ array, 0, actual_length, reinterpret_cast<const T *>(gDataPtr));
+ Advance(actual_num_bytes);
+ return array;
+}
+
+template <typename T>
+jbyteArray JNICALL ConsumeRemainingAsArray(JNIEnv &env, jobject self) {
+ return ConsumeIntegralArray<T>(env, self, std::numeric_limits<jint>::max());
+}
+
+template <typename T>
+T JNICALL ConsumeIntegralInRange(JNIEnv &env, jobject self, T min, T max) {
+ if (min > max) {
+ ThrowIllegalArgumentException(
+ env, absl::StrFormat(
+ "Consume*InRange: min must be <= max (got min: %d, max: %d)",
+ min, max));
+ return 0;
+ }
+
+ uint64_t range = static_cast<uint64_t>(max) - min;
+ uint64_t result = 0;
+ std::size_t offset = 0;
+
+ while (offset < 8 * sizeof(T) && (range >> offset) > 0 &&
+ gRemainingBytes != 0) {
+ --gRemainingBytes;
+ result = (result << 8u) | gDataPtr[gRemainingBytes];
+ offset += 8;
+ }
+
+ if (range != std::numeric_limits<T>::max())
+ // We accept modulo bias in favor of reading a dynamic number of bytes as
+ // this would make it harder for the fuzzer to mutate towards values from
+ // the table of recent compares.
+ result = result % (range + 1);
+
+ return static_cast<T>(min + result);
+}
+
+template <typename T>
+T JNICALL ConsumeIntegral(JNIEnv &env, jobject self) {
+ // First generate an unsigned value and then (safely) cast it to a signed
+ // integral type. By doing this rather than calling ConsumeIntegralInRange
+ // with bounds [signed_min, signed_max], we ensure that there is a direct
+ // correspondence between the consumed raw bytes and the result (e.g., 0
+ // corresponds to 0 and not to signed_min). This should help mutating
+ // towards entries of the table of recent compares.
+ using UnsignedT = typename std::make_unsigned<T>::type;
+ static_assert(
+ std::numeric_limits<UnsignedT>::is_modulo,
+ "Unsigned to signed conversion requires modulo-based overflow handling");
+ return static_cast<T>(ConsumeIntegralInRange<UnsignedT>(
+ env, self, 0, std::numeric_limits<UnsignedT>::max()));
+}
+
+bool JNICALL ConsumeBool(JNIEnv &env, jobject self) {
+ return ConsumeIntegral<uint8_t>(env, self) & 1u;
+}
+
+jchar ConsumeCharInternal(JNIEnv &env, jobject self, bool filter_surrogates) {
+ auto raw_codepoint = ConsumeIntegral<jchar>(env, self);
+ if (filter_surrogates && raw_codepoint >= 0xd800 && raw_codepoint < 0xe000)
+ raw_codepoint -= 0xd800;
+ return raw_codepoint;
+}
+
+jchar JNICALL ConsumeChar(JNIEnv &env, jobject self) {
+ return ConsumeCharInternal(env, self, false);
+}
+
+jchar JNICALL ConsumeCharNoSurrogates(JNIEnv &env, jobject self) {
+ return ConsumeCharInternal(env, self, true);
+}
+
+template <typename T>
+T JNICALL ConsumeProbability(JNIEnv &env, jobject self) {
+ using IntegralType =
+ typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t,
+ uint64_t>::type;
+ T result = static_cast<T>(ConsumeIntegral<IntegralType>(env, self));
+ result /= static_cast<T>(std::numeric_limits<IntegralType>::max());
+ return result;
+}
+
+template <typename T>
+T JNICALL ConsumeFloatInRange(JNIEnv &env, jobject self, T min, T max) {
+ if (min > max) {
+ ThrowIllegalArgumentException(
+ env, absl::StrFormat(
+ "Consume*InRange: min must be <= max (got min: %f, max: %f)",
+ min, max));
+ return 0.0;
+ }
+
+ T range;
+ T result = min;
+
+ // Deal with overflow, in the event min and max are very far apart
+ if (min < 0 && max > 0 && min + std::numeric_limits<T>::max() < max) {
+ range = (max / 2) - (min / 2);
+ if (ConsumeBool(env, self)) {
+ result += range;
+ }
+ } else {
+ range = max - min;
+ }
+
+ T probability = ConsumeProbability<T>(env, self);
+ return result + range * probability;
+}
+
+template <typename T>
+T JNICALL ConsumeRegularFloat(JNIEnv &env, jobject self) {
+ return ConsumeFloatInRange(env, self, std::numeric_limits<T>::lowest(),
+ std::numeric_limits<T>::max());
+}
+
+template <typename T>
+T JNICALL ConsumeFloat(JNIEnv &env, jobject self) {
+ if (!gRemainingBytes) return 0.0;
+
+ auto type_val = ConsumeIntegral<uint8_t>(env, self);
+
+ if (type_val <= 10) {
+ // Consume the same amount of bytes as for a regular float/double
+ ConsumeRegularFloat<T>(env, self);
+
+ switch (type_val) {
+ case 0:
+ return 0.0;
+ case 1:
+ return -0.0;
+ case 2:
+ return std::numeric_limits<T>::infinity();
+ case 3:
+ return -std::numeric_limits<T>::infinity();
+ case 4:
+ return std::numeric_limits<T>::quiet_NaN();
+ case 5:
+ return std::numeric_limits<T>::denorm_min();
+ case 6:
+ return -std::numeric_limits<T>::denorm_min();
+ case 7:
+ return std::numeric_limits<T>::min();
+ case 8:
+ return -std::numeric_limits<T>::min();
+ case 9:
+ return std::numeric_limits<T>::max();
+ case 10:
+ return -std::numeric_limits<T>::max();
+ default:
+ abort();
+ }
+ }
+
+ T regular = ConsumeRegularFloat<T>(env, self);
+ return regular;
+}
+
+// Polyfill for C++20 std::countl_one, which counts the number of leading ones
+// in an unsigned integer.
+inline __attribute__((always_inline)) uint8_t countl_one(uint8_t byte) {
+ // The result of __builtin_clz is undefined for 0.
+ if (byte == 0xFF) return 8;
+ return __builtin_clz(static_cast<uint8_t>(~byte)) - 24;
+}
+
+// Forces a byte to be a valid UTF-8 continuation byte.
+inline __attribute__((always_inline)) void ForceContinuationByte(
+ uint8_t &byte) {
+ byte = (byte | (1u << 7u)) & ~(1u << 6u);
+}
+
+constexpr uint8_t kTwoByteZeroLeadingByte = 0b11000000;
+constexpr uint8_t kTwoByteZeroContinuationByte = 0b10000000;
+constexpr uint8_t kThreeByteLowLeadingByte = 0b11100000;
+constexpr uint8_t kSurrogateLeadingByte = 0b11101101;
+
+enum class Utf8GenerationState {
+ LeadingByte_Generic,
+ LeadingByte_AfterBackslash,
+ ContinuationByte_Generic,
+ ContinuationByte_LowLeadingByte,
+ FirstContinuationByte_LowLeadingByte,
+ FirstContinuationByte_SurrogateLeadingByte,
+ FirstContinuationByte_Generic,
+ SecondContinuationByte_Generic,
+ LeadingByte_LowSurrogate,
+ FirstContinuationByte_LowSurrogate,
+ SecondContinuationByte_HighSurrogate,
+ SecondContinuationByte_LowSurrogate,
+};
+
+// Consumes up to `max_bytes` arbitrary bytes pointed to by `ptr` and returns a
+// valid "modified UTF-8" string of length at most `max_length` that resembles
+// the input bytes as closely as possible as well as the number of consumed
+// bytes. If `stop_on_slash` is true, then the string will end on the first
+// single consumed '\'.
+//
+// "Modified UTF-8" is the string encoding used by the JNI. It is the same as
+// the legacy encoding CESU-8, but with `\0` coded on two bytes. In these
+// encodings, code points requiring 4 bytes in modern UTF-8 are represented as
+// two surrogates, each of which is coded on 3 bytes.
+//
+// This function has been designed with the following goals in mind:
+// 1. The generated string should be biased towards containing ASCII characters
+// as these are often the ones that affect control flow directly.
+// 2. Correctly encoded data (e.g. taken from the table of recent compares)
+// should be emitted unchanged.
+// 3. The raw fuzzer input should be preserved as far as possible, but the
+// output must always be correctly encoded.
+//
+// The JVM accepts string in two encodings: UTF-16 and modified UTF-8.
+// Generating UTF-16 would make it harder to fulfill the first design goal and
+// would potentially hinder compatibility with corpora using the much more
+// widely used UTF-8 encoding, which is reasonably similar to modified UTF-8. As
+// a result, this function uses modified UTF-8.
+//
+// See Algorithm 1 of https://arxiv.org/pdf/2010.03090.pdf for more details on
+// the individual cases involved in determining the validity of a UTF-8 string.
+template <bool ascii_only, bool stop_on_backslash>
+std::pair<std::string, std::size_t> FixUpModifiedUtf8(const uint8_t *data,
+ std::size_t max_bytes,
+ jint max_length) {
+ std::string str;
+ // Every character in modified UTF-8 is coded on at most six bytes. Every
+ // consumed byte is transformed into at most one code unit, except for the
+ // case of a zero byte which requires two bytes.
+ if (max_bytes > std::numeric_limits<std::size_t>::max() / 2)
+ max_bytes = std::numeric_limits<std::size_t>::max() / 2;
+ if (ascii_only) {
+ str.reserve(
+ std::min(2 * static_cast<std::size_t>(max_length), 2 * max_bytes));
+ } else {
+ str.reserve(
+ std::min(6 * static_cast<std::size_t>(max_length), 2 * max_bytes));
+ }
+
+ Utf8GenerationState state = Utf8GenerationState::LeadingByte_Generic;
+ const uint8_t *pos = data;
+ const auto data_end = data + max_bytes;
+ for (std::size_t length = 0; length < max_length && pos != data_end; ++pos) {
+ uint8_t c = *pos;
+ if (ascii_only) {
+ // Clamp to 7-bit ASCII range.
+ c &= 0x7Fu;
+ }
+ // Fix up c or previously read bytes according to the value of c and the
+ // current state. In the end, add the fixed up code unit c to the string.
+ // Exception: The zero character has to be coded on two bytes and is the
+ // only case in which an iteration of the loop adds two code units.
+ switch (state) {
+ case Utf8GenerationState::LeadingByte_Generic: {
+ switch (ascii_only ? 0 : countl_one(c)) {
+ case 0: {
+ // valid - 1-byte code point (ASCII)
+ // The zero character has to be coded on two bytes in modified
+ // UTF-8.
+ if (c == 0) {
+ str += static_cast<char>(kTwoByteZeroLeadingByte);
+ c = kTwoByteZeroContinuationByte;
+ } else if (stop_on_backslash && c == '\\') {
+ state = Utf8GenerationState::LeadingByte_AfterBackslash;
+ // The slash either signals the end of the string or is skipped,
+ // so don't append anything.
+ continue;
+ }
+ // Remain in state LeadingByte.
+ ++length;
+ break;
+ }
+ case 1: {
+ // invalid - continuation byte at leader byte position
+ // Fix it up to be of the form 0b110XXXXX and fall through to the
+ // case of a 2-byte sequence.
+ c |= 1u << 6u;
+ c &= ~(1u << 5u);
+ [[fallthrough]];
+ }
+ case 2: {
+ // (most likely) valid - start of a 2-byte sequence
+ // ASCII characters must be coded on a single byte, so we must
+ // ensure that the lower two bits combined with the six non-header
+ // bits of the following byte do not form a 7-bit ASCII value. This
+ // could only be the case if at most the lowest bit is set.
+ if ((c & 0b00011110u) == 0) {
+ state = Utf8GenerationState::ContinuationByte_LowLeadingByte;
+ } else {
+ state = Utf8GenerationState::ContinuationByte_Generic;
+ }
+ break;
+ }
+ // The default case falls through to the case of three leading ones
+ // coming right after.
+ default: {
+ // invalid - at least four leading ones
+ // In the case of exactly four leading ones, this would be valid
+ // UTF-8, but is not valid in the JVM's modified UTF-8 encoding.
+ // Fix it up by clearing the fourth leading one and falling through
+ // to the 3-byte case.
+ c &= ~(1u << 4u);
+ [[fallthrough]];
+ }
+ case 3: {
+ // valid - start of a 3-byte sequence
+ if (c == kThreeByteLowLeadingByte) {
+ state = Utf8GenerationState::FirstContinuationByte_LowLeadingByte;
+ } else if (c == kSurrogateLeadingByte) {
+ state = Utf8GenerationState::
+ FirstContinuationByte_SurrogateLeadingByte;
+ } else {
+ state = Utf8GenerationState::FirstContinuationByte_Generic;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ case Utf8GenerationState::LeadingByte_AfterBackslash: {
+ if (c != '\\') {
+ // Mark the current byte as consumed.
+ ++pos;
+ goto done;
+ }
+ // A double backslash is consumed as a single one. As we skipped the
+ // first one, emit the second one as usual.
+ state = Utf8GenerationState::LeadingByte_Generic;
+ ++length;
+ break;
+ }
+ case Utf8GenerationState::ContinuationByte_LowLeadingByte: {
+ ForceContinuationByte(c);
+ // Preserve the zero character, which is coded on two bytes in modified
+ // UTF-8. In all other cases ensure that we are not incorrectly encoding
+ // an ASCII character on two bytes by setting the eigth least
+ // significant bit of the encoded value (second least significant bit of
+ // the leading byte).
+ auto previous_c = static_cast<uint8_t>(str.back());
+ if (previous_c != kTwoByteZeroLeadingByte ||
+ c != kTwoByteZeroContinuationByte) {
+ str.back() = static_cast<char>(previous_c | (1u << 1u));
+ }
+ state = Utf8GenerationState::LeadingByte_Generic;
+ ++length;
+ break;
+ }
+ case Utf8GenerationState::ContinuationByte_Generic: {
+ ForceContinuationByte(c);
+ state = Utf8GenerationState::LeadingByte_Generic;
+ ++length;
+ break;
+ }
+ case Utf8GenerationState::FirstContinuationByte_LowLeadingByte: {
+ ForceContinuationByte(c);
+ // Ensure that the current code point could not have been coded on two
+ // bytes. As two bytes encode up to 11 bits and three bytes encode up
+ // to 16 bits, we thus have to make it such that the five highest bits
+ // are not all zero. Four of these bits are the non-header bits of the
+ // leader byte. Thus, set the highest non-header bit in this byte (fifth
+ // highest in the encoded value).
+ c |= 1u << 5u;
+ state = Utf8GenerationState::SecondContinuationByte_Generic;
+ break;
+ }
+ case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte: {
+ ForceContinuationByte(c);
+ if (c & (1u << 5u)) {
+ // Start with a high surrogate (0xD800-0xDBFF). c contains the second
+ // byte and the first two bits of the third byte. The first two bits
+ // of this second byte are fixed to 10 (in 0x8-0xB).
+ c |= 1u << 5u;
+ c &= ~(1u << 4u);
+ // The high surrogate must be followed by a low surrogate.
+ state = Utf8GenerationState::SecondContinuationByte_HighSurrogate;
+ } else {
+ state = Utf8GenerationState::SecondContinuationByte_Generic;
+ }
+ break;
+ }
+ case Utf8GenerationState::FirstContinuationByte_Generic: {
+ ForceContinuationByte(c);
+ state = Utf8GenerationState::SecondContinuationByte_Generic;
+ break;
+ }
+ case Utf8GenerationState::SecondContinuationByte_HighSurrogate: {
+ ForceContinuationByte(c);
+ state = Utf8GenerationState::LeadingByte_LowSurrogate;
+ ++length;
+ break;
+ }
+ case Utf8GenerationState::SecondContinuationByte_LowSurrogate:
+ case Utf8GenerationState::SecondContinuationByte_Generic: {
+ ForceContinuationByte(c);
+ state = Utf8GenerationState::LeadingByte_Generic;
+ ++length;
+ break;
+ }
+ case Utf8GenerationState::LeadingByte_LowSurrogate: {
+ // We have to emit a low surrogate leading byte, which is a fixed value.
+ // We still consume a byte from the input to make fuzzer changes more
+ // stable and preserve valid surrogate pairs picked up from e.g. the
+ // table of recent compares.
+ c = kSurrogateLeadingByte;
+ state = Utf8GenerationState::FirstContinuationByte_LowSurrogate;
+ break;
+ }
+ case Utf8GenerationState::FirstContinuationByte_LowSurrogate: {
+ ForceContinuationByte(c);
+ // Low surrogates are code points in the range 0xDC00-0xDFFF. c contains
+ // the second byte and the first two bits of the third byte. The first
+ // two bits of this second byte are fixed to 11 (in 0xC-0xF).
+ c |= (1u << 5u) | (1u << 4u);
+ // The second continuation byte of a low surrogate is not restricted,
+ // but we need to track it differently to allow for correct backtracking
+ // if it isn't completed.
+ state = Utf8GenerationState::SecondContinuationByte_LowSurrogate;
+ break;
+ }
+ }
+ str += static_cast<uint8_t>(c);
+ }
+
+ // Backtrack the current incomplete character.
+ switch (state) {
+ case Utf8GenerationState::SecondContinuationByte_LowSurrogate:
+ str.pop_back();
+ [[fallthrough]];
+ case Utf8GenerationState::FirstContinuationByte_LowSurrogate:
+ str.pop_back();
+ [[fallthrough]];
+ case Utf8GenerationState::LeadingByte_LowSurrogate:
+ str.pop_back();
+ [[fallthrough]];
+ case Utf8GenerationState::SecondContinuationByte_Generic:
+ case Utf8GenerationState::SecondContinuationByte_HighSurrogate:
+ str.pop_back();
+ [[fallthrough]];
+ case Utf8GenerationState::ContinuationByte_Generic:
+ case Utf8GenerationState::ContinuationByte_LowLeadingByte:
+ case Utf8GenerationState::FirstContinuationByte_Generic:
+ case Utf8GenerationState::FirstContinuationByte_LowLeadingByte:
+ case Utf8GenerationState::FirstContinuationByte_SurrogateLeadingByte:
+ str.pop_back();
+ [[fallthrough]];
+ case Utf8GenerationState::LeadingByte_Generic:
+ case Utf8GenerationState::LeadingByte_AfterBackslash:
+ // No backtracking required.
+ break;
+ }
+
+done:
+ return std::make_pair(str, pos - data);
+}
+} // namespace
+
+namespace jazzer {
+// Exposed for testing only.
+std::pair<std::string, std::size_t> FixUpModifiedUtf8(const uint8_t *data,
+ std::size_t max_bytes,
+ jint max_length,
+ bool ascii_only,
+ bool stop_on_backslash) {
+ if (ascii_only) {
+ if (stop_on_backslash) {
+ return ::FixUpModifiedUtf8<true, true>(data, max_bytes, max_length);
+ } else {
+ return ::FixUpModifiedUtf8<true, false>(data, max_bytes, max_length);
+ }
+ } else {
+ if (stop_on_backslash) {
+ return ::FixUpModifiedUtf8<false, true>(data, max_bytes, max_length);
+ } else {
+ return ::FixUpModifiedUtf8<false, false>(data, max_bytes, max_length);
+ }
+ }
+}
+} // namespace jazzer
+
+namespace {
+jstring ConsumeStringInternal(JNIEnv &env, jint max_length, bool ascii_only,
+ bool stop_on_backslash) {
+ if (max_length < 0) {
+ ThrowIllegalArgumentException(env, "maxLength must not be negative");
+ return nullptr;
+ }
+
+ if (max_length == 0 || gRemainingBytes == 0) return env.NewStringUTF("");
+
+ if (gRemainingBytes == 1) {
+ Advance(1);
+ return env.NewStringUTF("");
+ }
+
+ std::size_t max_bytes = gRemainingBytes;
+ std::string str;
+ std::size_t consumed_bytes;
+ std::tie(str, consumed_bytes) = jazzer::FixUpModifiedUtf8(
+ gDataPtr, max_bytes, max_length, ascii_only, stop_on_backslash);
+ Advance(consumed_bytes);
+ return env.NewStringUTF(str.c_str());
+}
+
+jstring JNICALL ConsumeAsciiString(JNIEnv &env, jobject self, jint max_length) {
+ return ConsumeStringInternal(env, max_length, true, true);
+}
+
+jstring JNICALL ConsumeString(JNIEnv &env, jobject self, jint max_length) {
+ return ConsumeStringInternal(env, max_length, false, true);
+}
+
+jstring JNICALL ConsumeRemainingAsAsciiString(JNIEnv &env, jobject self) {
+ return ConsumeStringInternal(env, std::numeric_limits<jint>::max(), true,
+ false);
+}
+
+jstring JNICALL ConsumeRemainingAsString(JNIEnv &env, jobject self) {
+ return ConsumeStringInternal(env, std::numeric_limits<jint>::max(), false,
+ false);
+}
+
+std::size_t RemainingBytes(JNIEnv &env, jobject self) {
+ return gRemainingBytes;
+}
+
+const JNINativeMethod kFuzzedDataMethods[]{
+ {(char *)"consumeBoolean", (char *)"()Z", (void *)&ConsumeBool},
+ {(char *)"consumeByte", (char *)"()B", (void *)&ConsumeIntegral<jbyte>},
+ {(char *)"consumeByte", (char *)"(BB)B",
+ (void *)&ConsumeIntegralInRange<jbyte>},
+ {(char *)"consumeShort", (char *)"()S", (void *)&ConsumeIntegral<jshort>},
+ {(char *)"consumeShort", (char *)"(SS)S",
+ (void *)&ConsumeIntegralInRange<jshort>},
+ {(char *)"consumeInt", (char *)"()I", (void *)&ConsumeIntegral<jint>},
+ {(char *)"consumeInt", (char *)"(II)I",
+ (void *)&ConsumeIntegralInRange<jint>},
+ {(char *)"consumeLong", (char *)"()J", (void *)&ConsumeIntegral<jlong>},
+ {(char *)"consumeLong", (char *)"(JJ)J",
+ (void *)&ConsumeIntegralInRange<jlong>},
+ {(char *)"consumeFloat", (char *)"()F", (void *)&ConsumeFloat<jfloat>},
+ {(char *)"consumeRegularFloat", (char *)"()F",
+ (void *)&ConsumeRegularFloat<jfloat>},
+ {(char *)"consumeRegularFloat", (char *)"(FF)F",
+ (void *)&ConsumeFloatInRange<jfloat>},
+ {(char *)"consumeProbabilityFloat", (char *)"()F",
+ (void *)&ConsumeProbability<jfloat>},
+ {(char *)"consumeDouble", (char *)"()D", (void *)&ConsumeFloat<jdouble>},
+ {(char *)"consumeRegularDouble", (char *)"()D",
+ (void *)&ConsumeRegularFloat<jdouble>},
+ {(char *)"consumeRegularDouble", (char *)"(DD)D",
+ (void *)&ConsumeFloatInRange<jdouble>},
+ {(char *)"consumeProbabilityDouble", (char *)"()D",
+ (void *)&ConsumeProbability<jdouble>},
+ {(char *)"consumeChar", (char *)"()C", (void *)&ConsumeChar},
+ {(char *)"consumeChar", (char *)"(CC)C",
+ (void *)&ConsumeIntegralInRange<jchar>},
+ {(char *)"consumeCharNoSurrogates", (char *)"()C",
+ (void *)&ConsumeCharNoSurrogates},
+ {(char *)"consumeAsciiString", (char *)"(I)Ljava/lang/String;",
+ (void *)&ConsumeAsciiString},
+ {(char *)"consumeRemainingAsAsciiString", (char *)"()Ljava/lang/String;",
+ (void *)&ConsumeRemainingAsAsciiString},
+ {(char *)"consumeString", (char *)"(I)Ljava/lang/String;",
+ (void *)&ConsumeString},
+ {(char *)"consumeRemainingAsString", (char *)"()Ljava/lang/String;",
+ (void *)&ConsumeRemainingAsString},
+ {(char *)"consumeBooleans", (char *)"(I)[Z",
+ (void *)&ConsumeIntegralArray<jboolean>},
+ {(char *)"consumeBytes", (char *)"(I)[B",
+ (void *)&ConsumeIntegralArray<jbyte>},
+ {(char *)"consumeShorts", (char *)"(I)[S",
+ (void *)&ConsumeIntegralArray<jshort>},
+ {(char *)"consumeInts", (char *)"(I)[I",
+ (void *)&ConsumeIntegralArray<jint>},
+ {(char *)"consumeLongs", (char *)"(I)[J",
+ (void *)&ConsumeIntegralArray<jlong>},
+ {(char *)"consumeRemainingAsBytes", (char *)"()[B",
+ (void *)&ConsumeRemainingAsArray<jbyte>},
+ {(char *)"remainingBytes", (char *)"()I", (void *)&RemainingBytes},
+};
+const jint kNumFuzzedDataMethods =
+ sizeof(kFuzzedDataMethods) / sizeof(kFuzzedDataMethods[0]);
+} // namespace
+
+namespace jazzer {
+
+void SetUpFuzzedDataProvider(JNIEnv &env) {
+ jclass fuzzed_data_provider_class =
+ env.FindClass(kFuzzedDataProviderImplClass);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error("failed to find FuzzedDataProviderImpl class");
+ }
+ env.RegisterNatives(fuzzed_data_provider_class, kFuzzedDataMethods,
+ kNumFuzzedDataMethods);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(
+ "could not register native callbacks for FuzzedDataProvider");
+ }
+}
+
+void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size) {
+ gDataPtr = data;
+ gRemainingBytes = size;
+}
+} // namespace jazzer
diff --git a/driver/fuzzed_data_provider.h b/driver/fuzzed_data_provider.h
new file mode 100644
index 00000000..9b8faf78
--- /dev/null
+++ b/driver/fuzzed_data_provider.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <algorithm>
+#include <climits>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <initializer_list>
+#include <iostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace jazzer {
+
+constexpr char kFuzzedDataProviderImplClass[] =
+ "com/code_intelligence/jazzer/runtime/FuzzedDataProviderImpl";
+
+// Registers the native methods in FuzzedDataProvider.
+void SetUpFuzzedDataProvider(JNIEnv &env);
+
+// Feed the FuzzedDataProvider with a new data buffer. The buffer is accessed
+// by native code and not copied into the JVM, so this is cheap to call.
+void FeedFuzzedDataProvider(const uint8_t *data, std::size_t size);
+} // namespace jazzer
diff --git a/driver/fuzzed_data_provider_test.cpp b/driver/fuzzed_data_provider_test.cpp
new file mode 100644
index 00000000..210bf118
--- /dev/null
+++ b/driver/fuzzed_data_provider_test.cpp
@@ -0,0 +1,300 @@
+// Copyright 2021 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.
+
+#include "fuzzed_data_provider.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <random>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "fuzz_target_runner.h"
+#include "gflags/gflags.h"
+#include "gtest/gtest.h"
+#include "jvm_tooling.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+DECLARE_string(cp);
+DECLARE_string(jvm_args);
+DECLARE_string(instrumentation_excludes);
+
+DECLARE_string(target_class);
+DECLARE_string(target_args);
+
+namespace jazzer {
+
+std::pair<std::string, std::size_t> FixUpModifiedUtf8(const uint8_t* pos,
+ std::size_t max_bytes,
+ jint max_length,
+ bool ascii_only,
+ bool stop_on_backslash);
+
+std::pair<std::string, std::size_t> FixUpRemainingModifiedUtf8(
+ const std::string& str, bool ascii_only, bool stop_on_backslash) {
+ return FixUpModifiedUtf8(reinterpret_cast<const uint8_t*>(str.c_str()),
+ str.length(), std::numeric_limits<jint>::max(),
+ ascii_only, stop_on_backslash);
+}
+
+// Work around the fact that size_t is unsigned long on Linux and unsigned long
+// long on Windows.
+std::size_t operator"" _z(unsigned long long x) { return x; }
+
+using namespace std::literals::string_literals;
+TEST(FixUpModifiedUtf8Test, FullUtf8_ContinueOnBackslash) {
+ EXPECT_EQ(std::make_pair("jazzer"s, 6_z),
+ FixUpRemainingModifiedUtf8("jazzer"s, false, false));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\0zzer"s, false, false));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\0\0zzer"s, false, false));
+ EXPECT_EQ(std::make_pair("ja\\zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\\zzer"s, false, false));
+ EXPECT_EQ(std::make_pair("ja\\\\zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\\\\zzer"s, false, false));
+ EXPECT_EQ(std::make_pair("ۧ"s, 5_z),
+ FixUpRemainingModifiedUtf8(u8"ۧ"s, false, false));
+}
+
+TEST(FixUpModifiedUtf8Test, AsciiOnly_ContinueOnBackslash) {
+ EXPECT_EQ(std::make_pair("jazzer"s, 6_z),
+ FixUpRemainingModifiedUtf8("jazzer"s, true, false));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\0zzer"s, true, false));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\0\0zzer"s, true, false));
+ EXPECT_EQ(std::make_pair("ja\\zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\\zzer"s, true, false));
+ EXPECT_EQ(std::make_pair("ja\\\\zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\\\\zzer"s, true, false));
+ EXPECT_EQ(std::make_pair("\x62\x02\x2C\x43\x1F"s, 5_z),
+ FixUpRemainingModifiedUtf8(u8"ۧ"s, true, false));
+}
+
+TEST(FixUpModifiedUtf8Test, FullUtf8_StopOnBackslash) {
+ EXPECT_EQ(std::make_pair("jazzer"s, 6_z),
+ FixUpRemainingModifiedUtf8("jazzer"s, false, true));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\0zzer"s, false, true));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\0\0zzer"s, false, true));
+ EXPECT_EQ(std::make_pair("ja"s, 4_z),
+ FixUpRemainingModifiedUtf8("ja\\zzer"s, false, true));
+ EXPECT_EQ(std::make_pair("ja\\zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\\\\zzer"s, false, true));
+}
+
+TEST(FixUpModifiedUtf8Test, AsciiOnly_StopOnBackslash) {
+ EXPECT_EQ(std::make_pair("jazzer"s, 6_z),
+ FixUpRemainingModifiedUtf8("jazzer"s, true, true));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80zzer"s, 7_z),
+ FixUpRemainingModifiedUtf8("ja\0zzer"s, true, true));
+ EXPECT_EQ(std::make_pair("ja\xC0\x80\xC0\x80zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\0\0zzer"s, true, true));
+ EXPECT_EQ(std::make_pair("ja"s, 4_z),
+ FixUpRemainingModifiedUtf8("ja\\zzer"s, true, true));
+ EXPECT_EQ(std::make_pair("ja\\zzer"s, 8_z),
+ FixUpRemainingModifiedUtf8("ja\\\\zzer"s, true, true));
+}
+
+class FuzzedDataProviderTest : public ::testing::Test {
+ protected:
+ // After DestroyJavaVM() no new JVM instance can be created in the same
+ // process, so we set up a single JVM instance for this test binary which gets
+ // destroyed after all tests in this test suite have finished.
+ static void SetUpTestCase() {
+ FLAGS_instrumentation_excludes = "**";
+ using ::bazel::tools::cpp::runfiles::Runfiles;
+ Runfiles* runfiles = Runfiles::CreateForTest();
+ FLAGS_cp = runfiles->Rlocation(FLAGS_cp);
+
+ jvm_ = std::make_unique<JVM>("test_executable");
+ }
+
+ static void TearDownTestCase() { jvm_.reset(nullptr); }
+
+ static std::unique_ptr<JVM> jvm_;
+};
+
+std::unique_ptr<JVM> FuzzedDataProviderTest::jvm_ = nullptr;
+
+// see testdata/test/FuzzTargetWithDataProvider.java for the implementation
+// of the fuzz target that asserts that the correct values are received from
+// the data provider.
+const uint8_t kInput[] = {
+ // Bytes read from the start
+ 0x01, 0x02, // consumeBytes(2): {0x01, 0x02}
+
+ 'j', 'a', 'z', 'z', 'e', 'r', // consumeString(6): "jazzer"
+ 'j', 'a', 0x00, 'z', 'e', 'r', // consumeString(6): "ja\u0000zer"
+ 0xE2, 0x82, 0xAC, 0xC3, 0x9F, // consumeString(2): "€ẞ"
+
+ 'j', 'a', 'z', 'z', 'e', 'r', // consumeAsciiString(6): "jazzer"
+ 'j', 'a', 0x00, 'z', 'e', 'r', // consumeAsciiString(6): "ja\u0000zer"
+ 0xE2, 0x82, 0xAC, 0xC3,
+ 0x9F, // consumeAsciiString(5): "\u0062\u0002\u002C\u0043\u001F"
+
+ false, false, true, false,
+ true, // consumeBooleans(5): { false, false, true, false, true }
+ 0xEF, 0xDC, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76,
+ 0x98, 0xBA, 0xDC,
+ 0xFE, // consumeLongs(2): { 0x0123456789ABCDEF, 0xFEDCBA9876543210 }
+
+ 0x78, 0x56, 0x34, 0x12, // consumeInts(3): { 0x12345678 }
+ 0x56, 0x34, 0x12, // consumeLong():
+
+ // Bytes read from the end
+ 0x02, 0x03, 0x02, 0x04, // 4x pickValue in array with five elements
+
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 10, // -max for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 9, // max for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 8, // -min for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 7, // min for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 6, // -denorm_min for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 5, // denorm_min for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 4, // NaN for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 3, // -infinity for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 2, // infinity for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 1, // -0.0 for next consumeDouble
+ 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+ 0x78, // consumed but unused by consumeDouble()
+ 0, // 0.0 for next consumeDouble
+
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 10, // -max for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 9, // max for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 8, // -min for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 7, // min for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 6, // -denorm_min for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 5, // denorm_min for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 4, // NaN for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 3, // -infinity for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 2, // infinity for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 1, // -0.0 for next consumeFloat
+ 0x12, 0x34, 0x56, 0x78, 0x90, // consumed but unused by consumeFloat()
+ 0, // 0.0 for next consumeFloat
+
+ 0x88, 0xAB, 0x61, 0xCB, 0x32, 0xEB, 0x30,
+ 0xF9, // consumeDouble(13.37, 31.337): 30.859126145478349 (small range)
+ 0x51, 0xF6, 0x1F,
+ 0x3A, // consumeFloat(123.0, 777.0): 271.49084 (small range)
+ 0x11, 0x4D, 0xFD, 0x54, 0xD6, 0x3D, 0x43, 0x73,
+ 0x39, // consumeRegularDouble(): 8.0940194040236032e+307
+ 0x16, 0xCF, 0x3D, 0x29, 0x4A, // consumeRegularFloat(): -2.8546307e+38
+
+ 0x61, 0xCB, 0x32, 0xEB, 0x30, 0xF9, 0x51,
+ 0xF6, // consumeProbabilityDouble(): 0.96218831486039413
+ 0x1F, 0x3A, 0x11, 0x4D, // consumeProbabilityFloat(): 0.30104411
+ 0xFD, 0x54, 0xD6, 0x3D, 0x43, 0x73, 0x39,
+ 0x16, // consumeProbabilityDouble(): 0.086814121166605432
+ 0xCF, 0x3D, 0x29, 0x4A, // consumeProbabilityFloat(): 0.28969181
+
+ 0x01, // consumeInt(0x12345678, 0x12345679): 0x12345679
+ 0x78, // consumeInt(-0x12345678, -0x12345600): -0x12345600
+ 0x78, 0x56, 0x34, 0x12, // consumeInt(): 0x12345678
+
+ 0x02, // consumeByte(0x12, 0x22): 0x14
+ 0x7F, // consumeByte(): 0x7F
+
+ 0x01, // consumeBool(): true
+};
+
+TEST_F(FuzzedDataProviderTest, FuzzTargetWithDataProvider) {
+ FLAGS_target_class = "test/FuzzTargetWithDataProvider";
+ FLAGS_target_args = "";
+ FuzzTargetRunner fuzz_target_runner(*jvm_);
+
+ ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(kInput, sizeof(kInput)));
+}
+
+constexpr std::size_t kValidModifiedUtf8NumRuns = 10000;
+constexpr std::size_t kValidModifiedUtf8NumBytes = 100000;
+constexpr uint32_t kValidModifiedUtf8Seed = 0x12345678;
+
+TEST_F(FuzzedDataProviderTest, InvalidModifiedUtf8AfterFixup) {
+ auto modified_utf8_validator = jvm_->FindClass("test.ModifiedUtf8Encoder");
+ ASSERT_NE(nullptr, modified_utf8_validator);
+ auto string_to_modified_utf_bytes = jvm_->GetStaticMethodID(
+ modified_utf8_validator, "encode", "(Ljava/lang/String;)[B");
+ ASSERT_NE(nullptr, string_to_modified_utf_bytes);
+ auto& env = jvm_->GetEnv();
+ auto random_bytes = std::vector<uint8_t>(kValidModifiedUtf8NumBytes);
+ auto random = std::mt19937(kValidModifiedUtf8Seed);
+ for (bool ascii_only : {false, true}) {
+ for (bool stop_on_backslash : {false, true}) {
+ for (std::size_t i = 0; i < kValidModifiedUtf8NumRuns; ++i) {
+ std::generate(random_bytes.begin(), random_bytes.end(), random);
+ std::string fixed_string;
+ std::tie(fixed_string, std::ignore) = FixUpModifiedUtf8(
+ random_bytes.data(), random_bytes.size(),
+ std::numeric_limits<jint>::max(), ascii_only, stop_on_backslash);
+
+ jstring jni_fixed_string = env.NewStringUTF(fixed_string.c_str());
+ auto jni_roundtripped_bytes = (jbyteArray)env.CallStaticObjectMethod(
+ modified_utf8_validator, string_to_modified_utf_bytes,
+ jni_fixed_string);
+ ASSERT_FALSE(env.ExceptionCheck());
+ env.DeleteLocalRef(jni_fixed_string);
+ jint roundtripped_bytes_length =
+ env.GetArrayLength(jni_roundtripped_bytes);
+ jbyte* roundtripped_bytes =
+ env.GetByteArrayElements(jni_roundtripped_bytes, nullptr);
+ auto roundtripped_string =
+ std::string(reinterpret_cast<char*>(roundtripped_bytes),
+ roundtripped_bytes_length);
+ env.ReleaseByteArrayElements(jni_roundtripped_bytes, roundtripped_bytes,
+ JNI_ABORT);
+ env.DeleteLocalRef(jni_roundtripped_bytes);
+
+ // Verify that the bytes obtained from running our modified UTF-8 fix-up
+ // function remain unchanged when turned into a Java string and
+ // reencoded into modified UTF-8. This will only happen if the our
+ // fix-up function indeed returned valid modified UTF-8.
+ ASSERT_EQ(fixed_string, roundtripped_string);
+ }
+ }
+ }
+}
+} // namespace jazzer
diff --git a/driver/java_reproducer.cpp b/driver/java_reproducer.cpp
new file mode 100644
index 00000000..ed4c6755
--- /dev/null
+++ b/driver/java_reproducer.cpp
@@ -0,0 +1,79 @@
+// Copyright 2021 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.
+
+#include "java_reproducer.h"
+
+#include "fuzzed_data_provider.h"
+#include "jvm_tooling.h"
+
+namespace {
+const char kRecordingFuzzedDataProviderClass[] =
+ "com/code_intelligence/jazzer/runtime/RecordingFuzzedDataProvider";
+}
+
+namespace jazzer {
+jobject GetFuzzedDataProviderJavaObject(const JVM &jvm) {
+ static jobject java_object = nullptr;
+ if (java_object == nullptr) {
+ jclass java_class = jvm.FindClass(kFuzzedDataProviderImplClass);
+ jmethodID java_constructor = jvm.GetMethodID(java_class, "<init>", "()V");
+ jobject local_ref = jvm.GetEnv().NewObject(java_class, java_constructor);
+ // We leak a global reference here as it will be used until JVM exit.
+ java_object = jvm.GetEnv().NewGlobalRef(local_ref);
+ }
+ return java_object;
+}
+
+jobject GetRecordingFuzzedDataProviderJavaObject(const JVM &jvm) {
+ auto &env = jvm.GetEnv();
+ jclass java_class = jvm.FindClass(kRecordingFuzzedDataProviderClass);
+ jmethodID java_make_proxy = jvm.GetStaticMethodID(
+ java_class, "makeFuzzedDataProviderProxy",
+ "()Lcom/code_intelligence/jazzer/api/FuzzedDataProvider;", true);
+ jobject local_ref = env.CallStaticObjectMethod(java_class, java_make_proxy);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ // This global reference is deleted in SerializeRecordingFuzzedDataProvider.
+ jobject global_ref = env.NewGlobalRef(local_ref);
+ env.DeleteLocalRef(local_ref);
+ return global_ref;
+}
+
+std::string SerializeRecordingFuzzedDataProvider(const JVM &jvm,
+ jobject recorder) {
+ auto &env = jvm.GetEnv();
+ jclass java_class = jvm.FindClass(kRecordingFuzzedDataProviderClass);
+ jmethodID java_serialize =
+ jvm.GetStaticMethodID(java_class, "serializeFuzzedDataProviderProxy",
+ "(Lcom/code_intelligence/jazzer/api/"
+ "FuzzedDataProvider;)Ljava/lang/String;",
+ true);
+ auto serialized_recorder =
+ (jstring)env.CallStaticObjectMethod(java_class, java_serialize, recorder);
+ env.DeleteLocalRef(java_class);
+ env.DeleteGlobalRef(recorder);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ exit(1);
+ }
+ const char *serialized_recorder_cstr =
+ env.GetStringUTFChars(serialized_recorder, nullptr);
+ std::string out(serialized_recorder_cstr);
+ env.ReleaseStringUTFChars(serialized_recorder, serialized_recorder_cstr);
+ env.DeleteLocalRef(serialized_recorder);
+ return out;
+}
+} // namespace jazzer
diff --git a/driver/java_reproducer.h b/driver/java_reproducer.h
new file mode 100644
index 00000000..b3202b14
--- /dev/null
+++ b/driver/java_reproducer.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include "jvm_tooling.h"
+
+namespace jazzer {
+// Gets the single global reference to a Java FuzzedDataProvider object. The
+// object itself doesn't hold any state and only exists to make the UX better by
+// providing it as an argument to the fuzz target instead of relying on static
+// calls.
+jobject GetFuzzedDataProviderJavaObject(const JVM &jvm);
+
+jobject GetRecordingFuzzedDataProviderJavaObject(const JVM &jvm);
+
+std::string SerializeRecordingFuzzedDataProvider(const JVM &jvm,
+ jobject recorder);
+} // namespace jazzer
diff --git a/driver/java_reproducer_templates.h b/driver/java_reproducer_templates.h
new file mode 100644
index 00000000..7d58e1fa
--- /dev/null
+++ b/driver/java_reproducer_templates.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <string>
+
+constexpr const char *kBaseReproducer =
+ R"java(import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class Crash_$0 {
+ static final String base64Bytes = "$1";
+
+ public static void main(String[] args) {
+ ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
+ try {
+ Method fuzzerInitialize = $2.class.getMethod("fuzzerInitialize");
+ fuzzerInitialize.invoke(null);
+ } catch (NoSuchMethodException ignored) {
+ try {
+ Method fuzzerInitialize = $2.class.getMethod("fuzzerInitialize", String[].class);
+ fuzzerInitialize.invoke(null, (Object) args);
+ } catch (NoSuchMethodException ignored1) {
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ $3
+ $2.fuzzerTestOneInput(input);
+ }
+}
+)java";
+
+constexpr const char *kTestOneInputWithBytes =
+ "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);";
+
+constexpr const char *kTestOneInputWithData =
+ "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new "
+ "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes)"
+ ";";
diff --git a/driver/jvm_tooling.cpp b/driver/jvm_tooling.cpp
new file mode 100644
index 00000000..178eec02
--- /dev/null
+++ b/driver/jvm_tooling.cpp
@@ -0,0 +1,482 @@
+// Copyright 2021 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.
+
+#include "jvm_tooling.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/str_split.h"
+#include "coverage_tracker.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "libfuzzer_callbacks.h"
+#include "signal_handler.h"
+#include "tools/cpp/runfiles/runfiles.h"
+#include "utils.h"
+
+DEFINE_string(cp, ".",
+ "the classpath to use for fuzzing. Behaves analogously to java's "
+ "-cp (separator is ':' on Linux/macOS and ';' on Windows, escape "
+ "it with '\\').");
+DEFINE_string(jvm_args, "",
+ "arguments passed to the JVM (separator is ':' on Linux/macOS "
+ "and ';' on Windows, escape it with '\\')");
+DEFINE_string(additional_jvm_args, "",
+ "additional arguments passed to the JVM (separator is ':' on "
+ "Linux/macOS and ';' on Windows). Use this option to set further "
+ "JVM args that should not "
+ "interfere with those provided via --jvm_args.");
+DEFINE_string(agent_path, "", "location of the fuzzing instrumentation agent");
+
+// Arguments that are passed to the instrumentation agent.
+// The instrumentation agent takes arguments in the form
+// <option_1>=<option_1_val>,<option_2>=<option_2_val>,... To not expose this
+// format to the user the available options are defined here as flags and
+// combined during the initialization of the JVM.
+DEFINE_string(instrumentation_includes, "",
+ "list of glob patterns for classes that will be instrumented for "
+ "fuzzing. Separated by colon \":\"");
+DEFINE_string(instrumentation_excludes, "",
+ "list of glob patterns for classes that will not be instrumented "
+ "for fuzzing. Separated by colon \":\"");
+
+DEFINE_string(custom_hook_includes, "",
+ "list of glob patterns for classes that will only be "
+ "instrumented using custom hooks. Separated by colon \":\"");
+DEFINE_string(custom_hook_excludes, "",
+ "list of glob patterns for classes that will not be instrumented "
+ "using custom hooks. Separated by colon \":\"");
+DEFINE_string(custom_hooks, "",
+ "list of classes containing custom instrumentation hooks. "
+ "Separated by colon \":\"");
+DEFINE_string(
+ trace, "",
+ "list of instrumentation to perform separated by colon \":\". "
+ "Available options are cov, cmp, div, gep, all. These options "
+ "correspond to the \"-fsanitize-coverage=trace-*\" flags in clang.");
+DEFINE_string(
+ id_sync_file, "",
+ "path to a file that should be used to synchronize coverage IDs "
+ "between parallel fuzzing processes. Defaults to a temporary file "
+ "created for this purpose if running in parallel.");
+DEFINE_string(
+ dump_classes_dir, "",
+ "path to a directory in which Jazzer should dump the instrumented classes");
+
+DEFINE_bool(hooks, true,
+ "Use JVM hooks to provide coverage information to the fuzzer. The "
+ "fuzzer uses the coverage information to perform smarter input "
+ "selection and mutation. If set to false no "
+ "coverage information will be processed. This can be useful for "
+ "running a regression test on non-instrumented bytecode.");
+
+#ifdef _WIN32
+#define ARG_SEPARATOR ";"
+#else
+#define ARG_SEPARATOR ":"
+#endif
+
+// Called by the agent when
+// com.code_intelligence.jazzer.instrumentor.ClassInstrumentor is initialized.
+// This only happens when FLAGS_hooks is true.
+extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_jazzer_initialize(JavaVM *vm,
+ void *) {
+ if (!FLAGS_hooks) {
+ LOG(ERROR) << "JNI_OnLoad_jazzer_initialize called with --nohooks";
+ exit(1);
+ }
+ JNIEnv *env = nullptr;
+ jint result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_8);
+ if (result != JNI_OK) {
+ LOG(FATAL) << "Failed to get JNI environment";
+ exit(1);
+ }
+ jazzer::registerFuzzerCallbacks(*env);
+ jazzer::CoverageTracker::Setup(*env);
+ jazzer::SignalHandler::Setup(*env);
+ return JNI_VERSION_1_8;
+}
+
+namespace {
+constexpr auto kAgentBazelRunfilesPath = "jazzer/agent/jazzer_agent_deploy.jar";
+constexpr auto kAgentFileName = "jazzer_agent_deploy.jar";
+constexpr const char kExceptionUtilsClassName[] =
+ "com/code_intelligence/jazzer/runtime/ExceptionUtils";
+} // namespace
+
+namespace jazzer {
+
+void DumpJvmStackTraces() {
+ JavaVM *vm;
+ jsize num_vms;
+ JNI_GetCreatedJavaVMs(&vm, 1, &num_vms);
+ if (num_vms != 1) {
+ return;
+ }
+ JNIEnv *env = nullptr;
+ if (vm->AttachCurrentThread(reinterpret_cast<void **>(&env), nullptr) !=
+ JNI_OK) {
+ return;
+ }
+ jclass exceptionUtils = env->FindClass(kExceptionUtilsClassName);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ return;
+ }
+ jmethodID dumpStack =
+ env->GetStaticMethodID(exceptionUtils, "dumpAllStackTraces", "()V");
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ return;
+ }
+ env->CallStaticVoidMethod(exceptionUtils, dumpStack);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ return;
+ }
+ // Do not detach as we may be the main thread (but the JVM exits anyway).
+}
+
+std::string dirFromFullPath(const std::string &path) {
+ const auto pos = path.rfind(kPathSeparator);
+ if (pos != std::string::npos) {
+ return path.substr(0, pos);
+ }
+ return "";
+}
+
+// getInstrumentorAgentPath searches for the fuzzing instrumentation agent and
+// returns the location if it is found. Otherwise it calls exit(0).
+std::string getInstrumentorAgentPath(const std::string &executable_path) {
+ // User provided agent location takes precedence.
+ if (!FLAGS_agent_path.empty()) {
+ if (std::ifstream(FLAGS_agent_path).good()) return FLAGS_agent_path;
+ LOG(ERROR) << "Could not find " << kAgentFileName << " at \""
+ << FLAGS_agent_path << "\"";
+ exit(1);
+ }
+ // First check if we are running inside the Bazel tree and use the agent
+ // runfile.
+ {
+ using bazel::tools::cpp::runfiles::Runfiles;
+ std::string error;
+ std::unique_ptr<Runfiles> runfiles(
+ Runfiles::Create(executable_path, &error));
+ if (runfiles != nullptr) {
+ auto bazel_path = runfiles->Rlocation(kAgentBazelRunfilesPath);
+ if (!bazel_path.empty() && std::ifstream(bazel_path).good())
+ return bazel_path;
+ }
+ }
+
+ // If the agent is not in the bazel path we look next to the jazzer_driver
+ // binary.
+ const auto dir = dirFromFullPath(executable_path);
+ auto agent_path =
+ absl::StrFormat("%s%c%s", dir, kPathSeparator, kAgentFileName);
+ if (std::ifstream(agent_path).good()) return agent_path;
+ LOG(ERROR) << "Could not find " << kAgentFileName
+ << ". Please provide "
+ "the pathname via the --agent_path flag.";
+ exit(1);
+}
+
+std::string agentArgsFromFlags() {
+ std::vector<std::string> args;
+ for (const auto &flag_pair :
+ std::vector<std::pair<std::string, const std::string &>>{
+ // {<agent option>, <ref to glog flag> }
+ {"instrumentation_includes", FLAGS_instrumentation_includes},
+ {"instrumentation_excludes", FLAGS_instrumentation_excludes},
+ {"custom_hooks", FLAGS_custom_hooks},
+ {"custom_hook_includes", FLAGS_custom_hook_includes},
+ {"custom_hook_excludes", FLAGS_custom_hook_excludes},
+ {"trace", FLAGS_trace},
+ {"id_sync_file", FLAGS_id_sync_file},
+ {"dump_classes_dir", FLAGS_dump_classes_dir},
+ }) {
+ if (!flag_pair.second.empty()) {
+ args.push_back(flag_pair.first + "=" + flag_pair.second);
+ }
+ }
+ return absl::StrJoin(args, ",");
+}
+
+// Splits a string at the ARG_SEPARATOR unless it is escaped with a backslash.
+// Backslash itself can be escaped with another backslash.
+std::vector<std::string> splitEscaped(const std::string &str) {
+ // Protect \\ and \<separator> against splitting.
+ const std::string BACKSLASH_BACKSLASH_REPLACEMENT =
+ "%%JAZZER_BACKSLASH_BACKSLASH_REPLACEMENT%%";
+ const std::string BACKSLASH_SEPARATOR_REPLACEMENT =
+ "%%JAZZER_BACKSLASH_SEPARATOR_REPLACEMENT%%";
+ std::string protected_str =
+ absl::StrReplaceAll(str, {{"\\\\", BACKSLASH_BACKSLASH_REPLACEMENT}});
+ protected_str = absl::StrReplaceAll(
+ protected_str, {{"\\" ARG_SEPARATOR, BACKSLASH_SEPARATOR_REPLACEMENT}});
+
+ std::vector<std::string> parts = absl::StrSplit(protected_str, ARG_SEPARATOR);
+ std::transform(parts.begin(), parts.end(), parts.begin(),
+ [&BACKSLASH_SEPARATOR_REPLACEMENT,
+ &BACKSLASH_BACKSLASH_REPLACEMENT](const std::string &part) {
+ return absl::StrReplaceAll(
+ part,
+ {
+ {BACKSLASH_SEPARATOR_REPLACEMENT, ARG_SEPARATOR},
+ {BACKSLASH_BACKSLASH_REPLACEMENT, "\\"},
+ });
+ });
+
+ return parts;
+}
+
+JVM::JVM(const std::string &executable_path) {
+ // combine class path from command line flags and JAVA_FUZZER_CLASSPATH env
+ // variable
+ std::string class_path = absl::StrFormat("-Djava.class.path=%s", FLAGS_cp);
+ const auto class_path_from_env = std::getenv("JAVA_FUZZER_CLASSPATH");
+ if (class_path_from_env) {
+ class_path += absl::StrFormat(ARG_SEPARATOR "%s", class_path_from_env);
+ }
+ class_path += absl::StrFormat(ARG_SEPARATOR "%s",
+ getInstrumentorAgentPath(executable_path));
+ LOG(INFO) << "got class path " << class_path;
+
+ std::vector<JavaVMOption> options;
+ options.push_back(
+ JavaVMOption{.optionString = const_cast<char *>(class_path.c_str())});
+ // Set the maximum heap size to a value that is slightly smaller than
+ // libFuzzer's default rss_limit_mb. This prevents erroneous oom reports.
+ options.push_back(JavaVMOption{.optionString = (char *)"-Xmx1800m"});
+ options.push_back(JavaVMOption{.optionString = (char *)"-enableassertions"});
+ // Preserve and emit stack trace information even on hot paths.
+ // This may hurt performance, but also helps find flaky bugs.
+ options.push_back(
+ JavaVMOption{.optionString = (char *)"-XX:-OmitStackTraceInFastThrow"});
+ // Optimize GC for high throughput rather than low latency.
+ options.push_back(JavaVMOption{.optionString = (char *)"-XX:+UseParallelGC"});
+
+ // add additional jvm options set through command line flags
+ std::vector<std::string> jvm_args;
+ if (!FLAGS_jvm_args.empty()) {
+ jvm_args = splitEscaped(FLAGS_jvm_args);
+ }
+ for (const auto &arg : jvm_args) {
+ options.push_back(
+ JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
+ }
+ std::vector<std::string> additional_jvm_args;
+ if (!FLAGS_additional_jvm_args.empty()) {
+ additional_jvm_args = splitEscaped(FLAGS_additional_jvm_args);
+ }
+ for (const auto &arg : additional_jvm_args) {
+ options.push_back(
+ JavaVMOption{.optionString = const_cast<char *>(arg.c_str())});
+ }
+
+ std::string agent_jvm_arg;
+ if (FLAGS_hooks) {
+ agent_jvm_arg = absl::StrFormat("-javaagent:%s=%s",
+ getInstrumentorAgentPath(executable_path),
+ agentArgsFromFlags());
+ options.push_back(JavaVMOption{
+ .optionString = const_cast<char *>(agent_jvm_arg.c_str())});
+ }
+
+ JavaVMInitArgs jvm_init_args = {.version = JNI_VERSION_1_8,
+ .nOptions = (int)options.size(),
+ .options = options.data(),
+ .ignoreUnrecognized = JNI_FALSE};
+
+ auto ret = JNI_CreateJavaVM(&jvm_, (void **)&env_, &jvm_init_args);
+ if (ret != JNI_OK) {
+ throw std::runtime_error(
+ absl::StrFormat("JNI_CreateJavaVM returned code %d", ret));
+ }
+}
+
+JNIEnv &JVM::GetEnv() const { return *env_; }
+
+JVM::~JVM() { jvm_->DestroyJavaVM(); }
+
+jclass JVM::FindClass(std::string class_name) const {
+ auto &env = GetEnv();
+ std::replace(class_name.begin(), class_name.end(), '.', '/');
+ const auto ret = env.FindClass(class_name.c_str());
+ if (ret == nullptr) {
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(
+ absl::StrFormat("Could not find class %s", class_name));
+ } else {
+ throw std::runtime_error(absl::StrFormat(
+ "Java class '%s' not found without exception", class_name));
+ }
+ }
+ return ret;
+}
+
+jmethodID JVM::GetStaticMethodID(jclass jclass, const std::string &jmethod,
+ const std::string &signature,
+ bool is_required) const {
+ auto &env = GetEnv();
+ const auto ret =
+ env.GetStaticMethodID(jclass, jmethod.c_str(), signature.c_str());
+ if (ret == nullptr) {
+ if (is_required) {
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ }
+ throw std::runtime_error(
+ absl::StrFormat("Static method '%s' not found", jmethod));
+ } else {
+ LOG(INFO) << "did not find method " << jmethod << " with signature "
+ << signature;
+ env.ExceptionClear();
+ }
+ }
+ return ret;
+}
+
+jmethodID JVM::GetMethodID(jclass jclass, const std::string &jmethod,
+ const std::string &signature) const {
+ auto &env = GetEnv();
+ const auto ret = env.GetMethodID(jclass, jmethod.c_str(), signature.c_str());
+ if (ret == nullptr) {
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ }
+ throw std::runtime_error(absl::StrFormat("Method '%s' not found", jmethod));
+ }
+ return ret;
+}
+
+jfieldID JVM::GetStaticFieldID(jclass class_id, const std::string &field_name,
+ const std::string &type) const {
+ auto &env = GetEnv();
+ const auto ret =
+ env.GetStaticFieldID(class_id, field_name.c_str(), type.c_str());
+ if (ret == nullptr) {
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ }
+ throw std::runtime_error(
+ absl::StrFormat("Field '%s' not found", field_name));
+ }
+ return ret;
+}
+
+ExceptionPrinter::ExceptionPrinter(JVM &jvm)
+ : jvm_(jvm),
+ string_writer_class_(jvm.FindClass("java/io/StringWriter")),
+ string_writer_constructor_(
+ jvm.GetMethodID(string_writer_class_, "<init>", "()V")),
+ string_writer_to_string_method_(jvm.GetMethodID(
+ string_writer_class_, "toString", "()Ljava/lang/String;")),
+ print_writer_class_(jvm.FindClass("java/io/PrintWriter")),
+ print_writer_constructor_(jvm.GetMethodID(print_writer_class_, "<init>",
+ "(Ljava/io/Writer;)V")) {
+ auto throwable_class = jvm.FindClass("java/lang/Throwable");
+ print_stack_trace_method_ = jvm.GetMethodID(
+ throwable_class, "printStackTrace", "(Ljava/io/PrintWriter;)V");
+ if (FLAGS_hooks) {
+ exception_utils_ = jvm.FindClass(kExceptionUtilsClassName);
+ compute_dedup_token_method_ = jvm.GetStaticMethodID(
+ exception_utils_, "computeDedupToken", "(Ljava/lang/Throwable;)J");
+ preprocess_throwable_method_ =
+ jvm.GetStaticMethodID(exception_utils_, "preprocessThrowable",
+ "(Ljava/lang/Throwable;)Ljava/lang/Throwable;");
+ }
+}
+
+// The JNI way of writing:
+// StringWriter stringWriter = new StringWriter();
+// PrintWriter printWriter = new PrintWriter(stringWriter);
+// e.printStackTrace(printWriter);
+// return stringWriter.toString();
+std::string ExceptionPrinter::getStackTrace(jthrowable exception) const {
+ auto &env = jvm_.GetEnv();
+ if (exception == nullptr) {
+ return "";
+ }
+
+ auto string_writer =
+ env.NewObject(string_writer_class_, string_writer_constructor_);
+ if (string_writer == nullptr) {
+ env.ExceptionDescribe();
+ return "";
+ }
+ auto print_writer = env.NewObject(print_writer_class_,
+ print_writer_constructor_, string_writer);
+ if (print_writer == nullptr) {
+ env.ExceptionDescribe();
+ return "";
+ }
+
+ env.CallVoidMethod(exception, print_stack_trace_method_, print_writer);
+ env.DeleteLocalRef(print_writer);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ return "";
+ }
+ auto exception_string_object = reinterpret_cast<jstring>(
+ env.CallObjectMethod(string_writer, string_writer_to_string_method_));
+ env.DeleteLocalRef(string_writer);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ return "";
+ }
+
+ auto char_pointer = env.GetStringUTFChars(exception_string_object, nullptr);
+ std::string exception_string(char_pointer);
+ env.ReleaseStringUTFChars(exception_string_object, char_pointer);
+ env.DeleteLocalRef(exception_string_object);
+ return exception_string;
+}
+
+jthrowable ExceptionPrinter::preprocessException(jthrowable exception) const {
+ if (exception == nullptr) return nullptr;
+ auto &env = jvm_.GetEnv();
+ if (!FLAGS_hooks || !preprocess_throwable_method_) return exception;
+ auto processed_exception = (jthrowable)(env.CallStaticObjectMethod(
+ exception_utils_, preprocess_throwable_method_, exception));
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ return exception;
+ }
+ return processed_exception;
+}
+
+jlong ExceptionPrinter::computeDedupToken(jthrowable exception) const {
+ auto &env = jvm_.GetEnv();
+ if (!FLAGS_hooks || exception == nullptr ||
+ compute_dedup_token_method_ == nullptr)
+ return 0;
+ const auto dedup_token = env.CallStaticLongMethod(
+ exception_utils_, compute_dedup_token_method_, exception);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ return 0;
+ }
+ return dedup_token;
+}
+
+} // namespace jazzer
diff --git a/driver/jvm_tooling.h b/driver/jvm_tooling.h
new file mode 100644
index 00000000..be9582de
--- /dev/null
+++ b/driver/jvm_tooling.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+#include <string>
+
+namespace jazzer {
+
+void DumpJvmStackTraces();
+
+// JVM is a thin wrapper around JNI_CreateJavaVM and DestroyJavaVM. The JVM
+// instance is created inside the constructor with some default JNI options
+// + options which can be added to via command line flags.
+class JVM {
+ private:
+ JavaVM *jvm_;
+ JNIEnv *env_;
+
+ public:
+ // Creates a JVM instance with default options + options that were provided as
+ // command line flags.
+ explicit JVM(const std::string &executable_path);
+
+ // Destroy the running JVM instance.
+ ~JVM();
+
+ // Get the JNI environment for interaction with the running JVM instance.
+ JNIEnv &GetEnv() const;
+
+ jclass FindClass(std::string class_name) const;
+ jmethodID GetStaticMethodID(jclass class_id, const std::string &method_name,
+ const std::string &signature,
+ bool is_required = true) const;
+ jmethodID GetMethodID(jclass class_id, const std::string &method_name,
+ const std::string &signature) const;
+ jfieldID GetStaticFieldID(jclass jclass, const std::string &field_name,
+ const std::string &type) const;
+};
+
+// Adds convenience methods to convert a jvm exception to std::string
+// using StringWriter and PrintWriter. The stack trace can be subjected to
+// further processing, such as deduplication token computation and severity
+// annotation.
+class ExceptionPrinter {
+ private:
+ const JVM &jvm_;
+
+ jclass string_writer_class_;
+ jmethodID string_writer_constructor_;
+ jmethodID string_writer_to_string_method_;
+
+ jclass print_writer_class_;
+ jmethodID print_writer_constructor_;
+ jmethodID print_stack_trace_method_;
+
+ jclass exception_utils_;
+ jmethodID compute_dedup_token_method_;
+ jmethodID preprocess_throwable_method_;
+
+ protected:
+ explicit ExceptionPrinter(JVM &jvm);
+
+ // returns the current JVM exception stack trace as a string
+ std::string getStackTrace(jthrowable exception) const;
+ // augments the throwable with additional information such as severity markers
+ jthrowable preprocessException(jthrowable exception) const;
+ // returns a hash of the exception stack trace for deduplication purposes
+ jlong computeDedupToken(jthrowable exception) const;
+};
+
+} /* namespace jazzer */
diff --git a/driver/jvm_tooling_test.cpp b/driver/jvm_tooling_test.cpp
new file mode 100644
index 00000000..f2e8c66a
--- /dev/null
+++ b/driver/jvm_tooling_test.cpp
@@ -0,0 +1,199 @@
+// Copyright 2021 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.
+
+#include "jvm_tooling.h"
+
+#include "coverage_tracker.h"
+#include "fuzz_target_runner.h"
+#include "gflags/gflags.h"
+#include "gtest/gtest.h"
+#include "tools/cpp/runfiles/runfiles.h"
+
+DECLARE_string(cp);
+DECLARE_string(jvm_args);
+DECLARE_string(target_class);
+DECLARE_string(target_args);
+DECLARE_string(agent_path);
+DECLARE_string(instrumentation_excludes);
+
+#ifdef _WIN32
+#define ARG_SEPARATOR ";"
+#else
+#define ARG_SEPARATOR ":"
+#endif
+
+namespace jazzer {
+
+std::vector<std::string> splitOnSpace(const std::string &s);
+
+TEST(SpaceSplit, SpaceSplitSimple) {
+ ASSERT_EQ((std::vector<std::string>{"first", "se\\ cond", "third"}),
+ splitOnSpace("first se\\ cond third"));
+}
+
+class JvmToolingTest : public ::testing::Test {
+ protected:
+ // After DestroyJavaVM() no new JVM instance can be created in the same
+ // process, so we set up a single JVM instance for this test binary which gets
+ // destroyed after all tests in this test suite have finished.
+ static void SetUpTestCase() {
+ FLAGS_jvm_args =
+ "-Denv1=va\\" ARG_SEPARATOR "l1\\\\" ARG_SEPARATOR "-Denv2=val2";
+ FLAGS_instrumentation_excludes = "**";
+ using ::bazel::tools::cpp::runfiles::Runfiles;
+ Runfiles *runfiles = Runfiles::CreateForTest();
+ FLAGS_cp = runfiles->Rlocation(FLAGS_cp);
+
+ jvm_ = std::make_unique<JVM>("test_executable");
+ }
+
+ static void TearDownTestCase() { jvm_.reset(nullptr); }
+
+ static std::unique_ptr<JVM> jvm_;
+};
+
+std::unique_ptr<JVM> JvmToolingTest::jvm_ = nullptr;
+
+TEST_F(JvmToolingTest, ClassNotFound) {
+ ASSERT_THROW(jvm_->FindClass(""), std::runtime_error);
+ ASSERT_THROW(jvm_->FindClass("test.NonExistingClass"), std::runtime_error);
+ ASSERT_THROW(jvm_->FindClass("test/NonExistingClass"), std::runtime_error);
+}
+
+TEST_F(JvmToolingTest, ClassInClassPath) {
+ ASSERT_NE(nullptr, jvm_->FindClass("test.PropertyPrinter"));
+ ASSERT_NE(nullptr, jvm_->FindClass("test/PropertyPrinter"));
+}
+
+TEST_F(JvmToolingTest, JniProperties) {
+ auto property_printer_class = jvm_->FindClass("test.PropertyPrinter");
+ ASSERT_NE(nullptr, property_printer_class);
+ auto method_id =
+ jvm_->GetStaticMethodID(property_printer_class, "printProperty",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ ASSERT_NE(nullptr, method_id);
+
+ auto &env = jvm_->GetEnv();
+ for (const auto &el : std::vector<std::pair<std::string, std::string>>{
+ {"not set property", ""},
+ {"env1", "va" ARG_SEPARATOR "l1\\"},
+ {"env2", "val2"}}) {
+ jstring str = env.NewStringUTF(el.first.c_str());
+ auto ret = (jstring)env.CallStaticObjectMethod(property_printer_class,
+ method_id, str);
+ ASSERT_FALSE(env.ExceptionCheck());
+ if (el.second.empty()) {
+ ASSERT_EQ(nullptr, ret);
+ } else {
+ ASSERT_NE(nullptr, ret);
+ jboolean is_copy;
+ ASSERT_EQ(el.second, jvm_->GetEnv().GetStringUTFChars(ret, &is_copy));
+ }
+ }
+}
+
+TEST_F(JvmToolingTest, SimpleFuzzTarget) {
+ // see testdata/test/SimpleFuzzTarget.java for the implementation of the fuzz
+ // target
+ FLAGS_target_class = "test/SimpleFuzzTarget";
+ FLAGS_target_args = "";
+ FuzzTargetRunner fuzz_target_runner(*jvm_);
+
+ // normal case: fuzzerTestOneInput returns false
+ std::string input("random");
+ ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
+ (const uint8_t *)input.c_str(), input.size()));
+
+ // exception is thrown in fuzzerTestOneInput
+ input = "crash";
+ ASSERT_EQ(
+ RunResult::kException,
+ fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
+}
+
+class ExceptionPrinterTest : public ExceptionPrinter {
+ public:
+ ExceptionPrinterTest(JVM &jvm) : ExceptionPrinter(jvm), jvm_(jvm) {}
+
+ std::string TriggerJvmException() {
+ jclass illegal_argument_exception =
+ jvm_.FindClass("java.lang.IllegalArgumentException");
+ jvm_.GetEnv().ThrowNew(illegal_argument_exception, "Test");
+ jthrowable exception = jvm_.GetEnv().ExceptionOccurred();
+ jvm_.GetEnv().ExceptionClear();
+ return getStackTrace(exception);
+ }
+
+ private:
+ const JVM &jvm_;
+};
+
+TEST_F(JvmToolingTest, ExceptionPrinter) {
+ ExceptionPrinterTest exception_printer(*jvm_);
+ // a.k.a std::string.startsWith(java.lang...)
+ ASSERT_TRUE(exception_printer.TriggerJvmException().rfind(
+ "java.lang.IllegalArgumentException", 0) == 0);
+}
+
+TEST_F(JvmToolingTest, FuzzTargetWithInit) {
+ // see testdata/test/FuzzTargetWithInit.java for the implementation of the
+ // fuzz target. All string arguments provided in fuzzerInitialize(String[])
+ // will cause a crash if input in fuzzerTestOneInput(byte[]).
+ FLAGS_target_class = "test/FuzzTargetWithInit";
+ FLAGS_target_args = "crash_now crash_harder";
+ FuzzTargetRunner fuzz_target_runner(*jvm_);
+
+ // normal case: fuzzerTestOneInput returns false
+ std::string input("random");
+ ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
+ (const uint8_t *)input.c_str(), input.size()));
+
+ input = "crash_now";
+ ASSERT_EQ(
+ RunResult::kException,
+ fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
+
+ input = "this is harmless";
+ ASSERT_EQ(RunResult::kOk, fuzz_target_runner.Run(
+ (const uint8_t *)input.c_str(), input.size()));
+
+ input = "crash_harder";
+ ASSERT_EQ(
+ RunResult::kException,
+ fuzz_target_runner.Run((const uint8_t *)input.c_str(), input.size()));
+}
+
+TEST_F(JvmToolingTest, TestCoverageMap) {
+ CoverageTracker::Clear();
+ // check that after the initial clear the first coverage counter is 0
+ auto coverage_counters_array = CoverageTracker::GetCoverageCounters();
+ ASSERT_EQ(0, coverage_counters_array[0]);
+
+ FLAGS_target_class = "test/FuzzTargetWithCoverage";
+ FLAGS_target_args = "";
+ FuzzTargetRunner fuzz_target_runner(*jvm_);
+ // run a fuzz target input which will cause the first coverage counter to
+ // increase
+ fuzz_target_runner.Run(nullptr, 0);
+ ASSERT_EQ(1, coverage_counters_array[0]);
+ CoverageTracker::Clear();
+ // back to initial state
+ ASSERT_EQ(0, coverage_counters_array[0]);
+
+ // calling the fuzz target twice
+ fuzz_target_runner.Run(nullptr, 0);
+ fuzz_target_runner.Run(nullptr, 0);
+ ASSERT_EQ(2, coverage_counters_array[0]);
+}
+} // namespace jazzer
diff --git a/driver/libfuzzer_callbacks.cpp b/driver/libfuzzer_callbacks.cpp
new file mode 100644
index 00000000..5b7813dd
--- /dev/null
+++ b/driver/libfuzzer_callbacks.cpp
@@ -0,0 +1,411 @@
+// Copyright 2021 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.
+
+#include "libfuzzer_callbacks.h"
+
+#include <jni.h>
+
+#include <fstream>
+#include <iostream>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "sanitizer_hooks_with_pc.h"
+
+DEFINE_bool(
+ fake_pcs, false,
+ "Supply synthetic Java program counters to libFuzzer trace hooks to "
+ "make value profiling more effective. Enabled by default if "
+ "-use_value_profile=1 is specified.");
+
+namespace {
+
+const char kLibfuzzerTraceDataFlowHooksClass[] =
+ "com/code_intelligence/jazzer/runtime/"
+ "TraceDataFlowNativeCallbacks";
+
+extern "C" {
+void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1,
+ const void *s2, std::size_t n, int result);
+void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1,
+ const void *s2, std::size_t n1,
+ std::size_t n2, int result);
+void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1,
+ const char *s2, int result);
+void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1,
+ const char *s2, const char *result);
+void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
+void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
+
+void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
+
+void __sanitizer_cov_trace_div4(uint32_t val);
+void __sanitizer_cov_trace_div8(uint64_t val);
+
+void __sanitizer_cov_trace_gep(uintptr_t idx);
+}
+
+inline __attribute__((always_inline)) void *idToPc(jint id) {
+ return reinterpret_cast<void *>(static_cast<uintptr_t>(id));
+}
+
+void JNICALL libfuzzerStringCompareCallback(JNIEnv &env, jclass cls, jstring s1,
+ jstring s2, jint result, jint id) {
+ const char *s1_native = env.GetStringUTFChars(s1, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ std::size_t n1 = env.GetStringUTFLength(s1);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ const char *s2_native = env.GetStringUTFChars(s2, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ std::size_t n2 = env.GetStringUTFLength(s2);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ __sanitizer_weak_hook_compare_bytes(idToPc(id), s1_native, s2_native, n1, n2,
+ result);
+ env.ReleaseStringUTFChars(s1, s1_native);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ env.ReleaseStringUTFChars(s2, s2_native);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+}
+
+void JNICALL libfuzzerStringContainCallback(JNIEnv &env, jclass cls, jstring s1,
+ jstring s2, jint id) {
+ const char *s1_native = env.GetStringUTFChars(s1, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ const char *s2_native = env.GetStringUTFChars(s2, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ // libFuzzer currently ignores the result, which allows us to simply pass a
+ // valid but arbitrary pointer here instead of performing an actual strstr
+ // operation.
+ __sanitizer_weak_hook_strstr(idToPc(id), s1_native, s2_native, s1_native);
+ env.ReleaseStringUTFChars(s1, s1_native);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ env.ReleaseStringUTFChars(s2, s2_native);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+}
+
+void JNICALL libfuzzerByteCompareCallback(JNIEnv &env, jclass cls,
+ jbyteArray b1, jbyteArray b2,
+ jint result, jint id) {
+ jbyte *b1_native = env.GetByteArrayElements(b1, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ jbyte *b2_native = env.GetByteArrayElements(b2, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ jint b1_length = env.GetArrayLength(b1);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ jint b2_length = env.GetArrayLength(b2);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ __sanitizer_weak_hook_compare_bytes(idToPc(id), b1_native, b2_native,
+ b1_length, b2_length, result);
+ env.ReleaseByteArrayElements(b1, b1_native, JNI_ABORT);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ env.ReleaseByteArrayElements(b2, b2_native, JNI_ABORT);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+}
+
+void JNICALL libfuzzerLongCompareCallback(JNIEnv &env, jclass cls, jlong value1,
+ jlong value2, jint id) {
+ __sanitizer_cov_trace_cmp8(value1, value2);
+}
+
+void JNICALL libfuzzerLongCompareCallbackWithPc(JNIEnv &env, jclass cls,
+ jlong value1, jlong value2,
+ jint id) {
+ __sanitizer_cov_trace_cmp8_with_pc(idToPc(id), value1, value2);
+}
+
+void JNICALL libfuzzerIntCompareCallback(JNIEnv &env, jclass cls, jint value1,
+ jint value2, jint id) {
+ __sanitizer_cov_trace_cmp4(value1, value2);
+}
+
+void JNICALL libfuzzerIntCompareCallbackWithPc(JNIEnv &env, jclass cls,
+ jint value1, jint value2,
+ jint id) {
+ __sanitizer_cov_trace_cmp4_with_pc(idToPc(id), value1, value2);
+}
+
+void JNICALL libfuzzerSwitchCaseCallback(JNIEnv &env, jclass cls,
+ jlong switch_value,
+ jlongArray libfuzzer_case_values,
+ jint id) {
+ jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ __sanitizer_cov_trace_switch(switch_value,
+ reinterpret_cast<uint64_t *>(case_values));
+ env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+}
+
+void JNICALL libfuzzerSwitchCaseCallbackWithPc(JNIEnv &env, jclass cls,
+ jlong switch_value,
+ jlongArray libfuzzer_case_values,
+ jint id) {
+ jlong *case_values = env.GetLongArrayElements(libfuzzer_case_values, nullptr);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+ __sanitizer_cov_trace_switch_with_pc(
+ idToPc(id), switch_value, reinterpret_cast<uint64_t *>(case_values));
+ env.ReleaseLongArrayElements(libfuzzer_case_values, case_values, JNI_ABORT);
+ if (env.ExceptionCheck()) env.ExceptionDescribe();
+}
+
+void JNICALL libfuzzerLongDivCallback(JNIEnv &env, jclass cls, jlong value,
+ jint id) {
+ __sanitizer_cov_trace_div8(value);
+}
+
+void JNICALL libfuzzerLongDivCallbackWithPc(JNIEnv &env, jclass cls,
+ jlong value, jint id) {
+ __sanitizer_cov_trace_div8_with_pc(idToPc(id), value);
+}
+
+void JNICALL libfuzzerIntDivCallback(JNIEnv &env, jclass cls, jint value,
+ jint id) {
+ __sanitizer_cov_trace_div4(value);
+}
+
+void JNICALL libfuzzerIntDivCallbackWithPc(JNIEnv &env, jclass cls, jint value,
+ jint id) {
+ __sanitizer_cov_trace_div4_with_pc(idToPc(id), value);
+}
+
+void JNICALL libfuzzerGepCallback(JNIEnv &env, jclass cls, jlong idx, jint id) {
+ __sanitizer_cov_trace_gep(static_cast<uintptr_t>(idx));
+}
+
+void JNICALL libfuzzerGepCallbackWithPc(JNIEnv &env, jclass cls, jlong idx,
+ jint id) {
+ __sanitizer_cov_trace_gep_with_pc(idToPc(id), static_cast<uintptr_t>(idx));
+}
+
+void JNICALL libfuzzerPcIndirCallback(JNIEnv &env, jclass cls, jint caller_id,
+ jint callee_id) {
+ __sanitizer_cov_trace_pc_indir_with_pc(idToPc(caller_id),
+ static_cast<uintptr_t>(callee_id));
+}
+
+bool is_using_native_libraries = false;
+std::once_flag ignore_list_flag;
+std::vector<std::pair<uintptr_t, uintptr_t>> ignore_for_interception_ranges;
+
+extern "C" [[maybe_unused]] bool __sanitizer_weak_is_relevant_pc(
+ void *caller_pc) {
+ // If the fuzz target is not using native libraries, calls to strcmp, memcmp,
+ // etc. should never be intercepted. The values reported if they were at best
+ // duplicate the values received from our bytecode instrumentation and at
+ // worst pollute the table of recent compares with string internal to the JDK.
+ if (!is_using_native_libraries) return false;
+ // If the fuzz target is using native libraries, intercept calls only if they
+ // don't originate from those address ranges that are known to belong to the
+ // JDK.
+ return std::none_of(ignore_for_interception_ranges.cbegin(),
+ ignore_for_interception_ranges.cend(),
+ [caller_pc](const auto &range) {
+ uintptr_t start;
+ uintptr_t end;
+ std::tie(start, end) = range;
+ auto address = reinterpret_cast<uintptr_t>(caller_pc);
+ return start <= address && address <= end;
+ });
+}
+
+/**
+ * Adds the address ranges of executable segmentes of the library lib_name to
+ * the ignorelist for C standard library function interception (strcmp, memcmp,
+ * ...).
+ */
+void ignoreLibraryForInterception(const std::string &lib_name) {
+ const auto num_address_ranges = ignore_for_interception_ranges.size();
+ std::ifstream loaded_libs("/proc/self/maps");
+ if (!loaded_libs) {
+ // This early exit is taken e.g. on macOS, where /proc does not exist.
+ return;
+ }
+ std::string line;
+ while (std::getline(loaded_libs, line)) {
+ if (!absl::StrContains(line, lib_name)) continue;
+ // clang-format off
+ // A typical line looks as follows:
+ // 7f15356c9000-7f1536367000 r-xp 0020d000 fd:01 19275673 /usr/lib/jvm/java-15-openjdk-amd64/lib/server/libjvm.so
+ // clang-format on
+ std::vector<std::string_view> parts =
+ absl::StrSplit(line, ' ', absl::SkipEmpty());
+ if (parts.size() != 6) {
+ std::cout << "ERROR: Invalid format for /proc/self/maps\n"
+ << line << std::endl;
+ exit(1);
+ }
+ // Skip non-executable address rang"s.
+ if (!absl::StrContains(parts[1], "x")) continue;
+ std::string_view range_str = parts[0];
+ std::vector<std::string> range = absl::StrSplit(range_str, "-");
+ if (range.size() != 2) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ std::size_t pos;
+ auto start = std::stoull(range[0], &pos, 16);
+ if (pos != range[0].size()) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ auto end = std::stoull(range[1], &pos, 16);
+ if (pos != range[0].size()) {
+ std::cout
+ << "ERROR: Unexpected address range format in /proc/self/maps line: "
+ << range_str << std::endl;
+ exit(1);
+ }
+ ignore_for_interception_ranges.emplace_back(start, end);
+ }
+ const auto num_code_segments =
+ ignore_for_interception_ranges.size() - num_address_ranges;
+ LOG(INFO) << "added " << num_code_segments
+ << " code segment of native library " << lib_name
+ << " to interceptor ignorelist";
+}
+
+const std::vector<std::string> kLibrariesToIgnoreForInterception = {
+ // The driver executable itself can be treated just like a library.
+ "jazzer_driver", "libinstrument.so", "libjava.so",
+ "libjimage.so", "libjli.so", "libjvm.so",
+ "libnet.so", "libverify.so", "libzip.so",
+};
+
+void JNICALL handleLibraryLoad(JNIEnv &env, jclass cls) {
+ std::call_once(ignore_list_flag, [] {
+ LOG(INFO)
+ << "detected a native library load, enabling interception for libc "
+ "functions";
+ for (const auto &lib_name : kLibrariesToIgnoreForInterception)
+ ignoreLibraryForInterception(lib_name);
+ // Enable the ignore list after it has been populated since vector is not
+ // thread-safe with respect to concurrent writes and reads.
+ is_using_native_libraries = true;
+ });
+}
+
+void registerCallback(JNIEnv &env, const char *java_hooks_class_name,
+ const JNINativeMethod *methods, int num_methods) {
+ auto java_hooks_class = env.FindClass(java_hooks_class_name);
+ if (java_hooks_class == nullptr) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(
+ absl::StrFormat("could not find class %s", java_hooks_class_name));
+ }
+ LOG(INFO) << "registering hooks for class " << java_hooks_class_name;
+ env.RegisterNatives(java_hooks_class, methods, num_methods);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error("could not register native callbacks");
+ }
+}
+} // namespace
+
+namespace jazzer {
+
+bool registerFuzzerCallbacks(JNIEnv &env) {
+ if (FLAGS_fake_pcs) {
+ LOG(INFO) << "using callback variants with fake pcs";
+ CalibrateTrampoline();
+ }
+ {
+ JNINativeMethod string_methods[]{
+ {(char *)"traceMemcmp", (char *)"([B[BII)V",
+ (void *)&libfuzzerByteCompareCallback},
+ {(char *)"traceStrcmp",
+ (char *)"(Ljava/lang/String;Ljava/lang/String;II)V",
+ (void *)&libfuzzerStringCompareCallback},
+ {(char *)"traceStrstr",
+ (char *)"(Ljava/lang/String;Ljava/lang/String;I)V",
+ (void *)&libfuzzerStringContainCallback}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, string_methods,
+ sizeof(string_methods) / sizeof(string_methods[0]));
+ }
+
+ {
+ JNINativeMethod cmp_methods[]{
+ {(char *)"traceCmpLong", (char *)"(JJI)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerLongCompareCallbackWithPc
+ : &libfuzzerLongCompareCallback)},
+ {(char *)"traceCmpInt", (char *)"(III)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
+ : &libfuzzerIntCompareCallback)},
+ // libFuzzer internally treats const comparisons the same as
+ // non-constant cmps.
+ {(char *)"traceConstCmpInt", (char *)"(III)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerIntCompareCallbackWithPc
+ : &libfuzzerIntCompareCallback)},
+ {(char *)"traceSwitch", (char *)"(J[JI)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerSwitchCaseCallbackWithPc
+ : &libfuzzerSwitchCaseCallback)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, cmp_methods,
+ sizeof(cmp_methods) / sizeof(cmp_methods[0]));
+ }
+
+ {
+ JNINativeMethod div_methods[]{
+ {(char *)"traceDivLong", (char *)"(JI)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerLongDivCallbackWithPc
+ : &libfuzzerLongDivCallback)},
+ {(char *)"traceDivInt", (char *)"(II)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerIntDivCallbackWithPc
+ : &libfuzzerIntDivCallback)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, div_methods,
+ sizeof(div_methods) / sizeof(div_methods[0]));
+ }
+
+ {
+ JNINativeMethod gep_methods[]{
+ {(char *)"traceGep", (char *)"(JI)V",
+ (void *)(FLAGS_fake_pcs ? &libfuzzerGepCallbackWithPc
+ : &libfuzzerGepCallback)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, gep_methods,
+ sizeof(gep_methods) / sizeof(gep_methods[0]));
+ }
+
+ {
+ JNINativeMethod indir_methods[]{{(char *)"tracePcIndir", (char *)"(II)V",
+ (void *)(&libfuzzerPcIndirCallback)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, indir_methods,
+ sizeof(indir_methods) / sizeof(indir_methods[0]));
+ }
+
+ {
+ JNINativeMethod native_methods[]{{(char *)"handleLibraryLoad",
+ (char *)"()V",
+ (void *)(&handleLibraryLoad)}};
+
+ registerCallback(env, kLibfuzzerTraceDataFlowHooksClass, native_methods,
+ sizeof(native_methods) / sizeof(native_methods[0]));
+ }
+
+ return env.ExceptionCheck();
+}
+
+} // namespace jazzer
diff --git a/driver/libfuzzer_callbacks.h b/driver/libfuzzer_callbacks.h
new file mode 100644
index 00000000..985809a7
--- /dev/null
+++ b/driver/libfuzzer_callbacks.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+namespace jazzer {
+
+bool registerFuzzerCallbacks(JNIEnv &env);
+
+} // namespace jazzer
diff --git a/driver/libfuzzer_driver.cpp b/driver/libfuzzer_driver.cpp
new file mode 100644
index 00000000..57beef58
--- /dev/null
+++ b/driver/libfuzzer_driver.cpp
@@ -0,0 +1,187 @@
+// Copyright 2021 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.
+
+#include "libfuzzer_driver.h"
+
+#include <rules_jni.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <random>
+#include <string>
+#include <vector>
+
+#include "absl/strings/match.h"
+#include "absl/strings/str_format.h"
+#include "fuzz_target_runner.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "jvm_tooling.h"
+
+using namespace std::string_literals;
+
+// Defined by glog
+DECLARE_bool(log_prefix);
+
+// Defined in libfuzzer_callbacks.cpp
+DECLARE_bool(fake_pcs);
+
+// Defined in jvm_tooling.cpp
+DECLARE_string(id_sync_file);
+
+// Defined in fuzz_target_runner.cpp
+DECLARE_string(coverage_report);
+
+// This symbol is defined by sanitizers if linked into Jazzer or in
+// sanitizer_symbols.cpp if no sanitizer is used.
+extern "C" void __sanitizer_set_death_callback(void (*)());
+
+// We apply a patch to libFuzzer to make it call this function instead of
+// __sanitizer_set_death_callback to pass us the death callback.
+extern "C" [[maybe_unused]] void __jazzer_set_death_callback(
+ void (*callback)()) {
+ jazzer::AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_ = callback;
+ __sanitizer_set_death_callback(callback);
+}
+
+namespace {
+char *additional_arg;
+std::vector<char *> modified_argv;
+
+std::string GetNewTempFilePath() {
+ auto temp_dir = std::filesystem::temp_directory_path();
+
+ std::string temp_filename_suffix(32, '\0');
+ std::random_device rng;
+ std::uniform_int_distribution<short> dist(0, 'z' - 'a');
+ std::generate_n(temp_filename_suffix.begin(), temp_filename_suffix.length(),
+ [&rng, &dist] { return static_cast<char>('a' + dist(rng)); });
+
+ auto temp_path = temp_dir / ("jazzer-" + temp_filename_suffix);
+ if (std::filesystem::exists(temp_path))
+ throw std::runtime_error("Random temp file path exists: " +
+ temp_path.string());
+ return temp_path.string();
+}
+} // namespace
+
+namespace jazzer {
+// A libFuzzer-registered callback that outputs the crashing input, but does
+// not include a stack trace.
+void (*AbstractLibfuzzerDriver::libfuzzer_print_crashing_input_)() = nullptr;
+
+AbstractLibfuzzerDriver::AbstractLibfuzzerDriver(
+ int *argc, char ***argv, const std::string &usage_string) {
+ gflags::SetUsageMessage(usage_string);
+ // Disable glog log prefixes to mimic libFuzzer output.
+ FLAGS_log_prefix = false;
+ google::InitGoogleLogging((*argv)[0]);
+ rules_jni_init((*argv)[0]);
+
+ auto argv_start = *argv;
+ auto argv_end = *argv + *argc;
+
+ if (std::find(argv_start, argv_end, "-use_value_profile=1"s) != argv_end) {
+ FLAGS_fake_pcs = true;
+ }
+
+ // All libFuzzer flags start with a single dash, our arguments all start with
+ // a double dash. We can thus filter out the arguments meant for gflags by
+ // taking only those with a leading double dash.
+ std::vector<char *> our_args = {*argv_start};
+ std::copy_if(
+ argv_start, argv_end, std::back_inserter(our_args),
+ [](const auto arg) { return absl::StartsWith(std::string(arg), "--"); });
+ int our_argc = our_args.size();
+ char **our_argv = our_args.data();
+ // Let gflags consume its flags, but keep them in the argument list in case
+ // libFuzzer forwards the command line (e.g. with -jobs or -minimize_crash).
+ gflags::ParseCommandLineFlags(&our_argc, &our_argv, false);
+
+ if (std::any_of(argv_start, argv_end, [](const std::string_view &arg) {
+ return absl::StartsWith(arg, "-fork=") ||
+ absl::StartsWith(arg, "-jobs=") ||
+ absl::StartsWith(arg, "-merge=");
+ })) {
+ if (!FLAGS_coverage_report.empty()) {
+ LOG(WARNING) << "WARN: --coverage_report does not support parallel "
+ "fuzzing and has been disabled";
+ FLAGS_coverage_report = "";
+ }
+ if (FLAGS_id_sync_file.empty()) {
+ // Create an empty temporary file used for coverage ID synchronization and
+ // pass its path to the agent in every child process. This requires adding
+ // the argument to argv for it to be picked up by libFuzzer, which then
+ // forwards it to child processes.
+ FLAGS_id_sync_file = GetNewTempFilePath();
+ std::string new_arg =
+ absl::StrFormat("--id_sync_file=%s", FLAGS_id_sync_file);
+ // This argument can be accessed by libFuzzer at any (later) time and thus
+ // cannot be safely freed by us.
+ additional_arg = strdup(new_arg.c_str());
+ modified_argv = std::vector<char *>(argv_start, argv_end);
+ modified_argv.push_back(additional_arg);
+ // Terminate modified_argv.
+ modified_argv.push_back(nullptr);
+ // Modify argv and argc for libFuzzer. modified_argv must not be changed
+ // after this point.
+ *argc += 1;
+ *argv = modified_argv.data();
+ argv_start = *argv;
+ argv_end = *argv + *argc;
+ }
+ // Creates the file, truncating it if it exists.
+ std::ofstream touch_file(FLAGS_id_sync_file, std::ios_base::trunc);
+
+ auto cleanup_fn = [] {
+ try {
+ std::filesystem::remove(std::filesystem::path(FLAGS_id_sync_file));
+ } catch (...) {
+ // We should not throw exceptions during shutdown.
+ }
+ };
+ std::atexit(cleanup_fn);
+ }
+
+ initJvm(*argv_start);
+}
+
+void AbstractLibfuzzerDriver::initJvm(const std::string &executable_path) {
+ jvm_ = std::make_unique<jazzer::JVM>(executable_path);
+}
+
+LibfuzzerDriver::LibfuzzerDriver(int *argc, char ***argv)
+ : AbstractLibfuzzerDriver(argc, argv, getUsageString()) {
+ // the FuzzTargetRunner can only be initialized after the fuzzer callbacks
+ // have been registered otherwise link errors would occur
+ runner_ = std::make_unique<jazzer::FuzzTargetRunner>(*jvm_);
+}
+
+std::string LibfuzzerDriver::getUsageString() {
+ return R"(Test java fuzz targets using libFuzzer. Usage:
+ jazzer --cp=<java_class_path> --target_class=<fuzz_target_class> <libfuzzer_arguments...>)";
+}
+
+RunResult LibfuzzerDriver::TestOneInput(const uint8_t *data,
+ const std::size_t size) {
+ // pass the fuzzer input to the java fuzz target
+ return runner_->Run(data, size);
+}
+
+void LibfuzzerDriver::DumpReproducer(const uint8_t *data, std::size_t size) {
+ return runner_->DumpReproducer(data, size);
+}
+
+} // namespace jazzer
diff --git a/driver/libfuzzer_driver.h b/driver/libfuzzer_driver.h
new file mode 100644
index 00000000..557277a5
--- /dev/null
+++ b/driver/libfuzzer_driver.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/match.h"
+#include "fuzz_target_runner.h"
+#include "fuzzed_data_provider.h"
+#include "jvm_tooling.h"
+#include "libfuzzer_callbacks.h"
+#include "signal_handler.h"
+
+namespace jazzer {
+
+class AbstractLibfuzzerDriver {
+ public:
+ AbstractLibfuzzerDriver(int *argc, char ***argv,
+ const std::string &usage_string);
+
+ virtual ~AbstractLibfuzzerDriver() = default;
+
+ virtual RunResult TestOneInput(const uint8_t *data, std::size_t size) = 0;
+
+ // Default value of the libFuzzer -error_exitcode flag.
+ static constexpr int kErrorExitCode = 77;
+
+ // A libFuzzer-registered callback that outputs the crashing input, but does
+ // not include a stack trace.
+ static void (*libfuzzer_print_crashing_input_)();
+
+ protected:
+ // wrapper around the running jvm instance
+ std::unique_ptr<jazzer::JVM> jvm_;
+
+ private:
+ // forwards signals caught while the JVM is running
+ std::unique_ptr<jazzer::SignalHandler> signal_handler_;
+
+ void initJvm(const std::string &executable_path);
+};
+
+class LibfuzzerDriver : public AbstractLibfuzzerDriver {
+ public:
+ LibfuzzerDriver(int *argc, char ***argv);
+
+ RunResult TestOneInput(const uint8_t *data, std::size_t size) override;
+
+ ~LibfuzzerDriver() override = default;
+
+ void DumpReproducer(const uint8_t *data, std::size_t size);
+
+ private:
+ // initializes the fuzz target and invokes the TestOneInput function
+ std::unique_ptr<jazzer::FuzzTargetRunner> runner_;
+
+ static std::string getUsageString();
+};
+
+} // namespace jazzer
diff --git a/driver/libfuzzer_fuzz_target.cpp b/driver/libfuzzer_fuzz_target.cpp
new file mode 100644
index 00000000..d258e519
--- /dev/null
+++ b/driver/libfuzzer_fuzz_target.cpp
@@ -0,0 +1,86 @@
+// Copyright 2021 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.
+
+#include <iostream>
+
+#include "libfuzzer_driver.h"
+
+namespace {
+bool is_asan_active = false;
+}
+
+extern "C" {
+const char *__asan_default_options() {
+ is_asan_active = true;
+ // LeakSanitizer is not yet supported as it reports too many false positives
+ // due to how the JVM GC works.
+ // We use a distinguished exit code to recognize ASan crashes in tests.
+ // Also specify abort_on_error=0 explicitly since ASan aborts rather than
+ // exits on macOS by default, which would cause our exit code to be ignored.
+ return "abort_on_error=0,detect_leaks=0,exitcode=76";
+}
+
+const char *__ubsan_default_options() {
+ // We use a distinguished exit code to recognize UBSan crashes in tests.
+ // Also specify abort_on_error=0 explicitly since UBSan aborts rather than
+ // exits on macOS by default, which would cause our exit code to be ignored.
+ return "abort_on_error=0,exitcode=76";
+}
+}
+
+namespace {
+using Driver = jazzer::LibfuzzerDriver;
+
+std::unique_ptr<Driver> gLibfuzzerDriver;
+} // namespace
+
+extern "C" void driver_cleanup() {
+ // Free the libfuzzer driver which triggers a clean JVM shutdown.
+ gLibfuzzerDriver.reset(nullptr);
+}
+
+// Entry point called by libfuzzer before any LLVMFuzzerTestOneInput(...)
+// invocations.
+extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
+ if (is_asan_active) {
+ std::cerr << "WARN: Jazzer is not compatible with LeakSanitizer yet. Leaks "
+ "are not reported."
+ << std::endl;
+ }
+ gLibfuzzerDriver = std::make_unique<Driver>(argc, argv);
+ std::atexit(&driver_cleanup);
+ return 0;
+}
+
+// Called by the fuzzer for every fuzzing input.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, const size_t size) {
+ auto result = gLibfuzzerDriver->TestOneInput(data, size);
+ if (result != jazzer::RunResult::kOk) {
+ // Fuzzer triggered an exception or assertion in Java code. Skip the
+ // uninformative libFuzzer stack trace.
+ std::cerr << "== libFuzzer crashing input ==\n";
+ Driver::libfuzzer_print_crashing_input_();
+ // DumpReproducer needs to be called after libFuzzer printed its final
+ // stats as otherwise it would report incorrect coverage.
+ gLibfuzzerDriver->DumpReproducer(data, size);
+ if (result == jazzer::RunResult::kDumpAndContinue) {
+ // Continue fuzzing after printing the crashing input.
+ return 0;
+ }
+ // Exit directly without invoking libFuzzer's atexit hook.
+ driver_cleanup();
+ _Exit(Driver::kErrorExitCode);
+ }
+ return 0;
+}
diff --git a/driver/sanitizer_hooks_with_pc.cpp b/driver/sanitizer_hooks_with_pc.cpp
new file mode 100644
index 00000000..bb3ec5e1
--- /dev/null
+++ b/driver/sanitizer_hooks_with_pc.cpp
@@ -0,0 +1,187 @@
+// Copyright 2021 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.
+
+#include "sanitizer_hooks_with_pc.h"
+
+#include <cstddef>
+#include <cstdint>
+
+// libFuzzer's compare hooks obtain the caller's address from the compiler
+// builtin __builtin_return_adress. Since Java code will invoke the hooks always
+// from the same native function, this builtin would always return the same
+// value. Internally, the libFuzzer hooks call through to the always inlined
+// HandleCmp and thus can't be mimicked without patching libFuzzer.
+//
+// We solve this problem via an inline assembly trampoline construction that
+// translates a runtime argument `fake_pc` in the range [0, 512) into a call to
+// a hook with a fake return address whose lower 9 bits are `fake_pc` up to a
+// constant shift. This is achieved by pushing a return address pointing into
+// 512 ret instructions at offset `fake_pc` onto the stack and then jumping
+// directly to the address of the hook.
+//
+// Note: We only set the lowest 9 bits of the return address since only these
+// bits are used by the libFuzzer value profiling mode for integer compares, see
+// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390
+// as well as
+// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerValueBitMap.h#L34
+// ValueProfileMap.AddValue() truncates its argument to 16 bits and shifts the
+// PC to the left by log_2(128)=7, which means that only the lowest 16 - 7 bits
+// of the return address matter. String compare hooks use the lowest 12 bits,
+// but take the return address as an argument and thus don't require the
+// indirection through a trampoline.
+
+#define REPEAT_8(a) a a a a a a a a
+
+#define REPEAT_512(a) REPEAT_8(REPEAT_8(REPEAT_8(a)))
+
+// The first four registers to pass arguments in according to the
+// platform-specific x64 calling convention.
+#ifdef _WIN64
+#define REG_1 "rcx"
+#define REG_2 "rdx"
+#define REG_3 "r8"
+#define REG_4 "r9"
+#else
+#define REG_1 "rdi"
+#define REG_2 "rsi"
+#define REG_3 "rdx"
+#define REG_4 "rcx"
+#endif
+
+// Call the function at address `func` with arguments `arg1` and `arg2` while
+// ensuring that the return address is `fake_pc` up to a globally constant
+// offset.
+__attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2,
+ void *func, uint16_t fake_pc) {
+ // arg1 and arg2 have to be forwarded according to the x64 calling convention.
+ // We also fix func and fake_pc to their registers so that we can safely use
+ // rax below.
+ [[maybe_unused]] register uint64_t arg1_loc asm(REG_1) = arg1;
+ [[maybe_unused]] register uint64_t arg2_loc asm(REG_2) = arg2;
+ [[maybe_unused]] register void *func_loc asm(REG_3) = func;
+ [[maybe_unused]] register uint64_t fake_pc_loc asm(REG_4) = fake_pc;
+ asm volatile goto(
+ // Load RIP-relative address of the end of this function.
+ "lea %l[end_of_function](%%rip), %%rax \n\t"
+ "push %%rax \n\t"
+ // Load RIP-relative address of the ret sled into rax.
+ "lea ret_sled(%%rip), %%rax \n\t"
+ // Add the offset of the fake_pc-th ret.
+ "add %[fake_pc], %%rax \n\t"
+ // Push the fake return address pointing to that ret. The hook will return
+ // to it and then immediately return to the end of this function.
+ "push %%rax \n\t"
+ // Call func with the fake return address on the stack.
+ // Function arguments arg1 and arg2 are passed unchanged in the registers
+ // RDI and RSI as governed by the x64 calling convention.
+ "jmp *%[func] \n\t"
+ // Append a sled of 2^9=512 ret instructions.
+ "ret_sled: \n\t" REPEAT_512("ret \n\t")
+ :
+ : "r"(arg1_loc),
+ "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc)
+ : "memory"
+ : end_of_function);
+
+end_of_function:
+ return;
+}
+
+namespace {
+uintptr_t trampoline_offset = 0;
+}
+
+void set_trampoline_offset() {
+ // Stores the additive inverse of the current return address modulo 0x200u in
+ // trampoline_offset.
+ trampoline_offset =
+ 0x200u -
+ (reinterpret_cast<uintptr_t>(__builtin_return_address(0)) & 0x1FFu);
+}
+
+// Computes the additive shift that needs to be applied to the caller PC by
+// caller_pc_to_fake_pc to make caller PC and resulting fake return address
+// in their lowest 9 bite. This offset is constant for each binary, but may vary
+// based on code generation specifics. By calibrating the trampoline, the fuzzer
+// behavior is fully determined by the seed.
+void CalibrateTrampoline() {
+ trampoline(0, 0, reinterpret_cast<void *>(&set_trampoline_offset), 0);
+}
+
+// Masks any address down to its lower 9 bits, adjusting for the trampoline
+// shift.
+__attribute__((always_inline)) inline uint16_t caller_pc_to_fake_pc(
+ const void *caller_pc) {
+ return (reinterpret_cast<uintptr_t>(caller_pc) + trampoline_offset) & 0x1FFu;
+}
+
+// The original hooks exposed by libFuzzer. All of these get the caller's
+// address via __builtin_return_address(0).
+extern "C" {
+void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
+void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
+void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
+void __sanitizer_cov_trace_div4(uint32_t val);
+void __sanitizer_cov_trace_div8(uint64_t val);
+void __sanitizer_cov_trace_gep(uintptr_t idx);
+void __sanitizer_cov_trace_pc_indir(uintptr_t callee);
+}
+void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1,
+ uint32_t arg2) {
+ void *trace_cmp4 = reinterpret_cast<void *>(&__sanitizer_cov_trace_cmp4);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(arg1), static_cast<uint64_t>(arg2),
+ trace_cmp4, fake_pc);
+}
+
+void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1,
+ uint64_t arg2) {
+ void *trace_cmp8 = reinterpret_cast<void *>(&__sanitizer_cov_trace_cmp8);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(arg1), static_cast<uint64_t>(arg2),
+ trace_cmp8, fake_pc);
+}
+
+void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val,
+ uint64_t *cases) {
+ void *trace_switch = reinterpret_cast<void *>(&__sanitizer_cov_trace_switch);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(val), reinterpret_cast<uint64_t>(cases),
+ trace_switch, fake_pc);
+}
+
+void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val) {
+ void *trace_div4 = reinterpret_cast<void *>(&__sanitizer_cov_trace_div4);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(val), 0, trace_div4, fake_pc);
+}
+
+void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val) {
+ void *trace_div8 = reinterpret_cast<void *>(&__sanitizer_cov_trace_div8);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(val), 0, trace_div8, fake_pc);
+}
+
+void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx) {
+ void *trace_gep = reinterpret_cast<void *>(&__sanitizer_cov_trace_gep);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(idx), 0, trace_gep, fake_pc);
+}
+
+void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee) {
+ void *trace_pc_indir =
+ reinterpret_cast<void *>(&__sanitizer_cov_trace_pc_indir);
+ auto fake_pc = caller_pc_to_fake_pc(caller_pc);
+ trampoline(static_cast<uint64_t>(callee), 0, trace_pc_indir, fake_pc);
+}
diff --git a/driver/sanitizer_hooks_with_pc.h b/driver/sanitizer_hooks_with_pc.h
new file mode 100644
index 00000000..d9861315
--- /dev/null
+++ b/driver/sanitizer_hooks_with_pc.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+// This file declares variants of the libFuzzer compare, division, switch and
+// gep hooks that accept an additional caller_pc argument that can be used to
+// pass a custom value that is recorded as the caller's instruction pointer
+// ("program counter"). This allows synthetic program counters obtained from
+// Java coverage information to be used with libFuzzer's value profile, with
+// which it records detailed information about the result of compares and
+// associates it with particular coverage locations.
+//
+// Note: Only the lower 9 bits of the caller_pc argument are used by libFuzzer.
+extern "C" {
+void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1,
+ uint32_t arg2);
+void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1,
+ uint64_t arg2);
+
+void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val,
+ uint64_t *cases);
+
+void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val);
+void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val);
+
+void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx);
+
+void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee);
+}
+
+void CalibrateTrampoline();
diff --git a/driver/sanitizer_hooks_with_pc_test.cpp b/driver/sanitizer_hooks_with_pc_test.cpp
new file mode 100644
index 00000000..71d1527b
--- /dev/null
+++ b/driver/sanitizer_hooks_with_pc_test.cpp
@@ -0,0 +1,188 @@
+// Copyright 2021 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.
+
+#include "sanitizer_hooks_with_pc.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <iostream>
+
+#include "gtest/gtest.h"
+
+static std::vector<uint16_t> gCoverageMap(512);
+
+inline void __attribute__((always_inline)) RecordCoverage() {
+ auto return_address =
+ reinterpret_cast<uintptr_t>(__builtin_return_address(0));
+ auto idx = return_address & (gCoverageMap.size() - 1);
+ gCoverageMap[idx]++;
+}
+
+extern "C" {
+void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {
+ RecordCoverage();
+}
+
+void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {
+ RecordCoverage();
+}
+
+void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {
+ RecordCoverage();
+}
+
+void __sanitizer_cov_trace_div4(uint32_t val) { RecordCoverage(); }
+
+void __sanitizer_cov_trace_div8(uint64_t val) { RecordCoverage(); }
+
+void __sanitizer_cov_trace_gep(uintptr_t idx) { RecordCoverage(); }
+
+void __sanitizer_cov_trace_pc_indir(uintptr_t callee) { RecordCoverage(); }
+}
+
+void ClearCoverage() { std::fill(gCoverageMap.begin(), gCoverageMap.end(), 0); }
+
+bool HasAllPcsCovered() {
+ return 0 == std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0);
+}
+
+bool HasSingleCoveredPc() {
+ return gCoverageMap.size() - 1 ==
+ std::count(gCoverageMap.cbegin(), gCoverageMap.cend(), 0);
+}
+
+std::string PrettyPrintCoverage() {
+ std::ostringstream out;
+ std::size_t break_after = 16;
+ out << "Coverage:" << std::endl;
+ for (uintptr_t i = 0; i < gCoverageMap.size(); i++) {
+ out << (gCoverageMap[i] ? "X" : "_");
+ if (i % break_after == break_after - 1) out << std::endl;
+ }
+ return out.str();
+}
+
+class TestFakePcTrampoline : public ::testing::Test {
+ protected:
+ TestFakePcTrampoline() {
+ ClearCoverage();
+ CalibrateTrampoline();
+ }
+};
+
+TEST_F(TestFakePcTrampoline, TraceCmp4Direct) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_cmp4(i, i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceCmp8Direct) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_cmp8(i, i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceSwitchDirect) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_switch(i, nullptr);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceDiv4Direct) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_div4(i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceDiv8Direct) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_div8(i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceGepDirect) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_gep(i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TracePcIndirDirect) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_pc_indir(i);
+ }
+ EXPECT_TRUE(HasSingleCoveredPc()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceCmp4Trampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_cmp4_with_pc(reinterpret_cast<void *>(i), i, i);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceCmp8Trampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_cmp8_with_pc(reinterpret_cast<void *>(i), i, i);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceSwitchTrampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_switch_with_pc(reinterpret_cast<void *>(i), i,
+ nullptr);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceDiv4Trampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_div4_with_pc(reinterpret_cast<void *>(i), i);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceDiv8Trampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_div8_with_pc(reinterpret_cast<void *>(i), i);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TraceGepTrampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_gep_with_pc(reinterpret_cast<void *>(i), i);
+ EXPECT_EQ(1, gCoverageMap[i]);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
+
+TEST_F(TestFakePcTrampoline, TracePcIndirTrampoline) {
+ for (uint32_t i = 0; i < gCoverageMap.size(); ++i) {
+ __sanitizer_cov_trace_pc_indir_with_pc(reinterpret_cast<void *>(i), i);
+ }
+ EXPECT_TRUE(HasAllPcsCovered()) << PrettyPrintCoverage();
+}
diff --git a/driver/sanitizer_symbols.cpp b/driver/sanitizer_symbols.cpp
new file mode 100644
index 00000000..10255ef1
--- /dev/null
+++ b/driver/sanitizer_symbols.cpp
@@ -0,0 +1,29 @@
+// Copyright 2021 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.
+
+// Called in libfuzzer_driver.cpp.
+extern "C" void __sanitizer_set_death_callback(void (*)()) {}
+
+// Suppress libFuzzer warnings about missing sanitizer methods in non-sanitizer
+// builds.
+extern "C" int __sanitizer_acquire_crash_state() { return 1; }
+
+namespace jazzer {
+void DumpJvmStackTraces();
+}
+
+// Dump a JVM stack trace on timeouts.
+extern "C" void __sanitizer_print_stack_trace() {
+ jazzer::DumpJvmStackTraces();
+}
diff --git a/driver/sanitizer_symbols_for_tests.cpp b/driver/sanitizer_symbols_for_tests.cpp
new file mode 100644
index 00000000..7d84feac
--- /dev/null
+++ b/driver/sanitizer_symbols_for_tests.cpp
@@ -0,0 +1,45 @@
+// Copyright 2021 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.
+
+#include <cstddef>
+#include <cstdint>
+
+// Symbols exported by libFuzzer that are required by libfuzzer_callbacks and
+// CoverageTracker.
+extern "C" {
+void __sanitizer_cov_8bit_counters_init(uint8_t *start, uint8_t *end) {}
+void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
+ const uintptr_t *pcs_end) {}
+size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries) {
+ *pc_entries = new uintptr_t[0];
+ return 0;
+}
+void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1,
+ const void *s2, std::size_t n, int result) {}
+void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1,
+ const char *s2, int result) {}
+void __sanitizer_weak_hook_compare_bytes(void *caller_pc, const void *s1,
+ const void *s2, std::size_t n1,
+ std::size_t n2, int result) {}
+void __sanitizer_weak_hook_strstr(void *caller_pc, const char *s1,
+ const char *s2, int result) {}
+void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2) {}
+void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {}
+void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {}
+void __sanitizer_cov_trace_div4(uint32_t val) {}
+void __sanitizer_cov_trace_div8(uint64_t val) {}
+void __sanitizer_cov_trace_gep(uintptr_t idx) {}
+void __sanitizer_cov_trace_pc_indir(uintptr_t callee) {}
+void __sanitizer_set_death_callback(void (*callback)()) {}
+}
diff --git a/driver/signal_handler.cpp b/driver/signal_handler.cpp
new file mode 100644
index 00000000..05e5953a
--- /dev/null
+++ b/driver/signal_handler.cpp
@@ -0,0 +1,66 @@
+// Copyright 2021 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.
+
+#include "signal_handler.h"
+
+#include <jni.h>
+
+#include <atomic>
+#include <csignal>
+#include <stdexcept>
+
+constexpr auto kSignalHandlerClass =
+ "com/code_intelligence/jazzer/runtime/SignalHandler";
+
+// Handles SIGINT raised while running Java code.
+void JNICALL handleInterrupt(JNIEnv, jclass) {
+ static std::atomic<bool> already_exiting{false};
+ if (!already_exiting.exchange(true)) {
+ // Let libFuzzer exit gracefully when the JVM received SIGINT.
+ raise(SIGUSR1);
+ } else {
+ // Exit libFuzzer forcefully on repeated SIGINTs.
+ raise(SIGTERM);
+ }
+}
+
+namespace jazzer {
+void SignalHandler::Setup(JNIEnv &env) {
+ jclass signal_handler_class = env.FindClass(kSignalHandlerClass);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error("could not find signal handler class");
+ }
+ JNINativeMethod signal_handler_methods[]{
+ {(char *)"handleInterrupt", (char *)"()V", (void *)&handleInterrupt},
+ };
+ env.RegisterNatives(signal_handler_class, signal_handler_methods, 1);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error(
+ "could not register native callbacks 'handleInterrupt'");
+ }
+ jmethodID setup_signal_handlers_method_ =
+ env.GetStaticMethodID(signal_handler_class, "setupSignalHandlers", "()V");
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error("could not find setupSignalHandlers method");
+ }
+ env.CallStaticVoidMethod(signal_handler_class, setup_signal_handlers_method_);
+ if (env.ExceptionCheck()) {
+ env.ExceptionDescribe();
+ throw std::runtime_error("failed to set up signal handlers");
+ }
+}
+} // namespace jazzer
diff --git a/driver/signal_handler.h b/driver/signal_handler.h
new file mode 100644
index 00000000..d0d17121
--- /dev/null
+++ b/driver/signal_handler.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <jni.h>
+
+namespace jazzer {
+// SignalHandler registers handlers for signals (e.g. SIGINT) in Java and
+// notifies the driver via native callbacks when the handlers fire.
+class SignalHandler {
+ public:
+ // Set up handlers for signal in Java.
+ static void Setup(JNIEnv &env);
+};
+} // namespace jazzer
diff --git a/driver/test_main.cpp b/driver/test_main.cpp
new file mode 100644
index 00000000..bf33517f
--- /dev/null
+++ b/driver/test_main.cpp
@@ -0,0 +1,22 @@
+// Copyright 2021 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.
+
+#include "gflags/gflags.h"
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+ return RUN_ALL_TESTS();
+}
diff --git a/driver/testdata/BUILD.bazel b/driver/testdata/BUILD.bazel
new file mode 100644
index 00000000..8dd67e12
--- /dev/null
+++ b/driver/testdata/BUILD.bazel
@@ -0,0 +1,10 @@
+java_binary(
+ name = "fuzz_target_mocks",
+ srcs = glob(["test/*.java"]),
+ create_executable = False,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//agent/src/main/java/com/code_intelligence/jazzer/api",
+ "//agent/src/main/java/com/code_intelligence/jazzer/runtime",
+ ],
+)
diff --git a/driver/testdata/test/FuzzTargetWithCoverage.java b/driver/testdata/test/FuzzTargetWithCoverage.java
new file mode 100644
index 00000000..599b1fa8
--- /dev/null
+++ b/driver/testdata/test/FuzzTargetWithCoverage.java
@@ -0,0 +1,28 @@
+// Copyright 2021 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 test;
+
+import com.code_intelligence.jazzer.runtime.CoverageMap;
+
+public class FuzzTargetWithCoverage {
+ public static void fuzzerTestOneInput(byte[] input) {
+ // manually increase the first coverage counter
+ byte counter = CoverageMap.mem.get(0);
+ counter++;
+ if (counter == 0)
+ counter--;
+ CoverageMap.mem.put(0, counter);
+ }
+}
diff --git a/driver/testdata/test/FuzzTargetWithDataProvider.java b/driver/testdata/test/FuzzTargetWithDataProvider.java
new file mode 100644
index 00000000..fc5bc1b0
--- /dev/null
+++ b/driver/testdata/test/FuzzTargetWithDataProvider.java
@@ -0,0 +1,114 @@
+// Copyright 2021 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 test;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+class FuzzTargetWithDataProvider {
+ public static <T extends Comparable<T>> void assertEqual(T a, T b) {
+ if (a.compareTo(b) != 0) {
+ throw new IllegalArgumentException("Expected: " + a + ", got: " + b);
+ }
+ }
+
+ public strictfp static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ assertEqual(true, data.consumeBoolean());
+
+ assertEqual((byte) 0x7F, data.consumeByte());
+ assertEqual((byte) 0x14, data.consumeByte((byte) 0x12, (byte) 0x22));
+
+ assertEqual(0x12345678, data.consumeInt());
+ assertEqual(-0x12345600, data.consumeInt(-0x12345678, -0x12345600));
+ assertEqual(0x12345679, data.consumeInt(0x12345678, 0x12345679));
+
+ assertEqual(true, Arrays.equals(new byte[] {0x01, 0x02}, data.consumeBytes(2)));
+
+ assertEqual("jazzer", data.consumeString(6));
+ assertEqual("ja\u0000zer", data.consumeString(6));
+ assertEqual("ۧ", data.consumeString(2));
+
+ assertEqual("jazzer", data.consumeAsciiString(6));
+ assertEqual("ja\u0000zer", data.consumeAsciiString(6));
+ assertEqual("\u0062\u0002\u002C\u0043\u001F", data.consumeAsciiString(5));
+
+ assertEqual(true,
+ Arrays.equals(new boolean[] {false, false, true, false, true}, data.consumeBooleans(5)));
+ assertEqual(true,
+ Arrays.equals(new long[] {0x0123456789abdcefL, 0xfedcba9876543210L}, data.consumeLongs(2)));
+
+ assertEqual((float) 0.28969181, data.consumeProbabilityFloat());
+ assertEqual(0.086814121166605432, data.consumeProbabilityDouble());
+ assertEqual((float) 0.30104411, data.consumeProbabilityFloat());
+ assertEqual(0.96218831486039413, data.consumeProbabilityDouble());
+
+ assertEqual((float) -2.8546307e+38, data.consumeRegularFloat());
+ assertEqual(8.0940194040236032e+307, data.consumeRegularDouble());
+ assertEqual((float) 271.49084, data.consumeRegularFloat((float) 123.0, (float) 777.0));
+ assertEqual(30.859126145478349, data.consumeRegularDouble(13.37, 31.337));
+
+ assertEqual((float) 0.0, data.consumeFloat());
+ assertEqual((float) -0.0, data.consumeFloat());
+ assertEqual(Float.POSITIVE_INFINITY, data.consumeFloat());
+ assertEqual(Float.NEGATIVE_INFINITY, data.consumeFloat());
+ assertEqual(true, Float.isNaN(data.consumeFloat()));
+ assertEqual(Float.MIN_VALUE, data.consumeFloat());
+ assertEqual(-Float.MIN_VALUE, data.consumeFloat());
+ assertEqual(Float.MIN_NORMAL, data.consumeFloat());
+ assertEqual(-Float.MIN_NORMAL, data.consumeFloat());
+ assertEqual(Float.MAX_VALUE, data.consumeFloat());
+ assertEqual(-Float.MAX_VALUE, data.consumeFloat());
+
+ assertEqual(0.0, data.consumeDouble());
+ assertEqual(-0.0, data.consumeDouble());
+ assertEqual(Double.POSITIVE_INFINITY, data.consumeDouble());
+ assertEqual(Double.NEGATIVE_INFINITY, data.consumeDouble());
+ assertEqual(true, Double.isNaN(data.consumeDouble()));
+ assertEqual(Double.MIN_VALUE, data.consumeDouble());
+ assertEqual(-Double.MIN_VALUE, data.consumeDouble());
+ assertEqual(Double.MIN_NORMAL, data.consumeDouble());
+ assertEqual(-Double.MIN_NORMAL, data.consumeDouble());
+ assertEqual(Double.MAX_VALUE, data.consumeDouble());
+ assertEqual(-Double.MAX_VALUE, data.consumeDouble());
+
+ int[] array = {0, 1, 2, 3, 4};
+ assertEqual(4, data.pickValue(array));
+ assertEqual(2, (int) data.pickValue(Arrays.stream(array).boxed().toArray()));
+ assertEqual(3, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toList())));
+ assertEqual(2, data.pickValue(Arrays.stream(array).boxed().collect(Collectors.toSet())));
+
+ // Buffer is almost depleted at this point.
+ assertEqual(7, data.remainingBytes());
+ assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(3)));
+ assertEqual(7, data.remainingBytes());
+ assertEqual(true, Arrays.equals(new int[] {0x12345678}, data.consumeInts(3)));
+ assertEqual(3, data.remainingBytes());
+ assertEqual(0x123456L, data.consumeLong());
+
+ // Buffer has been fully consumed at this point
+ assertEqual(0, data.remainingBytes());
+ assertEqual(0, data.consumeInt());
+ assertEqual(0.0, data.consumeDouble());
+ assertEqual(-13.37, data.consumeRegularDouble(-13.37, 31.337));
+ assertEqual(true, Arrays.equals(new byte[0], data.consumeBytes(4)));
+ assertEqual(true, Arrays.equals(new long[0], data.consumeLongs(4)));
+ assertEqual("", data.consumeRemainingAsAsciiString());
+ assertEqual("", data.consumeRemainingAsString());
+ assertEqual("", data.consumeAsciiString(100));
+ assertEqual("", data.consumeString(100));
+ }
+}
diff --git a/driver/testdata/test/FuzzTargetWithInit.java b/driver/testdata/test/FuzzTargetWithInit.java
new file mode 100644
index 00000000..86aed82b
--- /dev/null
+++ b/driver/testdata/test/FuzzTargetWithInit.java
@@ -0,0 +1,30 @@
+// Copyright 2021 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 test;
+
+class FuzzTargetWithInit {
+ static String[] crashOnString;
+ public static void fuzzerInitialize(String[] args) {
+ crashOnString = args;
+ }
+ public static void fuzzerTestOneInput(byte[] input) {
+ String inputString = new String(input);
+ for (String crashString : crashOnString) {
+ if (inputString.equals(crashString)) {
+ throw new RuntimeException("triggered the exception");
+ }
+ }
+ }
+}
diff --git a/driver/testdata/test/ModifiedUtf8Encoder.java b/driver/testdata/test/ModifiedUtf8Encoder.java
new file mode 100644
index 00000000..b460c81c
--- /dev/null
+++ b/driver/testdata/test/ModifiedUtf8Encoder.java
@@ -0,0 +1,41 @@
+// Copyright 2021 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 test;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+
+final class ModifiedUtf8Encoder {
+ // Encodes a string in the JVM's modified UTF-8 encoding.
+ static public byte[] encode(String value) {
+ // Modified UTF-8 is almost the same as CESU-8, the only difference being that the zero
+ // character is coded on two bytes.
+ byte[] cesuBytes = value.getBytes(Charset.forName("CESU-8"));
+ ArrayList<Byte> modifiedUtf8Bytes = new ArrayList<>();
+ for (byte cesuByte : cesuBytes) {
+ if (cesuByte != 0) {
+ modifiedUtf8Bytes.add(cesuByte);
+ } else {
+ modifiedUtf8Bytes.add((byte) 0xC0);
+ modifiedUtf8Bytes.add((byte) 0x80);
+ }
+ }
+ byte[] out = new byte[modifiedUtf8Bytes.size()];
+ for (int i = 0; i < modifiedUtf8Bytes.size(); i++) {
+ out[i] = modifiedUtf8Bytes.get(i);
+ }
+ return out;
+ }
+}
diff --git a/driver/testdata/test/PropertyPrinter.java b/driver/testdata/test/PropertyPrinter.java
new file mode 100644
index 00000000..97345acd
--- /dev/null
+++ b/driver/testdata/test/PropertyPrinter.java
@@ -0,0 +1,22 @@
+// Copyright 2021 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 test;
+
+// Class used for testing
+class PropertyPrinter {
+ public static String printProperty(String property) {
+ return System.getProperty(property);
+ }
+}
diff --git a/driver/testdata/test/SimpleFuzzTarget.java b/driver/testdata/test/SimpleFuzzTarget.java
new file mode 100644
index 00000000..5657e416
--- /dev/null
+++ b/driver/testdata/test/SimpleFuzzTarget.java
@@ -0,0 +1,25 @@
+// Copyright 2021 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 test;
+
+class SimpleFuzzTarget {
+ public static void fuzzerTestOneInput(byte[] input) {
+ String inputString = new String(input);
+ System.err.println("got input " + inputString);
+ if (inputString.startsWith("crash")) {
+ throw new RuntimeException("exception triggered in fuzz target");
+ }
+ }
+}
diff --git a/driver/utils.cpp b/driver/utils.cpp
new file mode 100644
index 00000000..4d8042e3
--- /dev/null
+++ b/driver/utils.cpp
@@ -0,0 +1,208 @@
+// Copyright 2021 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.
+
+#include "utils.h"
+
+#include <cstdint>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+namespace {
+// BEGIN: Obtained from https://github.com/x42/liboauth/blob/master/src/sha1.c:
+/* This code is public-domain - it is based on libcrypt
+ * placed in the public domain by Wei Dai and other contributors.
+ */
+
+#ifdef __BIG_ENDIAN__
+#define SHA_BIG_ENDIAN
+#elif defined __LITTLE_ENDIAN__
+/* override */
+#elif defined __BYTE_ORDER
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define SHA_BIG_ENDIAN
+#endif
+#else // ! defined __LITTLE_ENDIAN__
+#include <endian.h> // machine/endian.h
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define SHA_BIG_ENDIAN
+#endif
+#endif
+
+/* header */
+
+#define HASH_LENGTH 20
+#define BLOCK_LENGTH 64
+
+typedef struct sha1nfo {
+ uint32_t buffer[BLOCK_LENGTH / 4];
+ uint32_t state[HASH_LENGTH / 4];
+ uint32_t byteCount;
+ uint8_t bufferOffset;
+ uint8_t keyBuffer[BLOCK_LENGTH];
+ uint8_t innerHash[HASH_LENGTH];
+} sha1nfo;
+
+/* public API - prototypes - TODO: doxygen*/
+
+/**
+ */
+void sha1_init(sha1nfo *s);
+/**
+ */
+void sha1_writebyte(sha1nfo *s, uint8_t data);
+/**
+ */
+void sha1_write(sha1nfo *s, const char *data, size_t len);
+/**
+ */
+uint8_t *sha1_result(sha1nfo *s);
+
+/* code */
+#define SHA1_K0 0x5a827999
+#define SHA1_K20 0x6ed9eba1
+#define SHA1_K40 0x8f1bbcdc
+#define SHA1_K60 0xca62c1d6
+
+void sha1_init(sha1nfo *s) {
+ s->state[0] = 0x67452301;
+ s->state[1] = 0xefcdab89;
+ s->state[2] = 0x98badcfe;
+ s->state[3] = 0x10325476;
+ s->state[4] = 0xc3d2e1f0;
+ s->byteCount = 0;
+ s->bufferOffset = 0;
+}
+
+uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
+ return ((number << bits) | (number >> (32 - bits)));
+}
+
+void sha1_hashBlock(sha1nfo *s) {
+ uint8_t i;
+ uint32_t a, b, c, d, e, t;
+
+ a = s->state[0];
+ b = s->state[1];
+ c = s->state[2];
+ d = s->state[3];
+ e = s->state[4];
+ for (i = 0; i < 80; i++) {
+ if (i >= 16) {
+ t = s->buffer[(i + 13) & 15] ^ s->buffer[(i + 8) & 15] ^
+ s->buffer[(i + 2) & 15] ^ s->buffer[i & 15];
+ s->buffer[i & 15] = sha1_rol32(t, 1);
+ }
+ if (i < 20) {
+ t = (d ^ (b & (c ^ d))) + SHA1_K0;
+ } else if (i < 40) {
+ t = (b ^ c ^ d) + SHA1_K20;
+ } else if (i < 60) {
+ t = ((b & c) | (d & (b | c))) + SHA1_K40;
+ } else {
+ t = (b ^ c ^ d) + SHA1_K60;
+ }
+ t += sha1_rol32(a, 5) + e + s->buffer[i & 15];
+ e = d;
+ d = c;
+ c = sha1_rol32(b, 30);
+ b = a;
+ a = t;
+ }
+ s->state[0] += a;
+ s->state[1] += b;
+ s->state[2] += c;
+ s->state[3] += d;
+ s->state[4] += e;
+}
+
+void sha1_addUncounted(sha1nfo *s, uint8_t data) {
+ uint8_t *const b = (uint8_t *)s->buffer;
+#ifdef SHA_BIG_ENDIAN
+ b[s->bufferOffset] = data;
+#else
+ b[s->bufferOffset ^ 3] = data;
+#endif
+ s->bufferOffset++;
+ if (s->bufferOffset == BLOCK_LENGTH) {
+ sha1_hashBlock(s);
+ s->bufferOffset = 0;
+ }
+}
+
+void sha1_writebyte(sha1nfo *s, uint8_t data) {
+ ++s->byteCount;
+ sha1_addUncounted(s, data);
+}
+
+void sha1_write(sha1nfo *s, const char *data, size_t len) {
+ for (; len--;) sha1_writebyte(s, (uint8_t)*data++);
+}
+
+void sha1_pad(sha1nfo *s) {
+ // Implement SHA-1 padding (fips180-2 §5.1.1)
+
+ // Pad with 0x80 followed by 0x00 until the end of the block
+ sha1_addUncounted(s, 0x80);
+ while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
+
+ // Append length in the last 8 bytes
+ sha1_addUncounted(s, 0); // We're only using 32 bit lengths
+ sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
+ sha1_addUncounted(s, 0); // So zero pad the top bits
+ sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
+ sha1_addUncounted(
+ s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
+ sha1_addUncounted(s, s->byteCount >> 13); // byte.
+ sha1_addUncounted(s, s->byteCount >> 5);
+ sha1_addUncounted(s, s->byteCount << 3);
+}
+
+uint8_t *sha1_result(sha1nfo *s) {
+ // Pad to complete the last block
+ sha1_pad(s);
+
+#ifndef SHA_BIG_ENDIAN
+ // Swap byte order back
+ int i;
+ for (i = 0; i < 5; i++) {
+ s->state[i] = (((s->state[i]) << 24) & 0xff000000) |
+ (((s->state[i]) << 8) & 0x00ff0000) |
+ (((s->state[i]) >> 8) & 0x0000ff00) |
+ (((s->state[i]) >> 24) & 0x000000ff);
+ }
+#endif
+
+ // Return pointer to hash (20 characters)
+ return (uint8_t *)s->state;
+}
+// END: Obtained from https://github.com/x42/liboauth/blob/master/src/sha1.c:
+} // namespace
+
+namespace jazzer {
+std::string Sha1Hash(const uint8_t *data, size_t size) {
+ sha1nfo hasher;
+ sha1_init(&hasher);
+ sha1_write(&hasher, reinterpret_cast<const char *>(data), size);
+ const uint8_t *hash = sha1_result(&hasher);
+ std::ostringstream out;
+ for (size_t i = 0; i < HASH_LENGTH; ++i) {
+ // Cast required because uint8_t would print as a char.
+ out << std::hex << std::setfill('0') << std::setw(2)
+ << static_cast<uint32_t>(hash[i]);
+ }
+ return out.str();
+}
+} // namespace jazzer
diff --git a/driver/utils.h b/driver/utils.h
new file mode 100644
index 00000000..99d7b60e
--- /dev/null
+++ b/driver/utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+
+namespace jazzer {
+
+#if defined(_WIN32) || defined(_WIN64)
+constexpr auto kPathSeparator = '\\';
+#else
+constexpr auto kPathSeparator = '/';
+#endif
+
+std::string Sha1Hash(const uint8_t *data, size_t size);
+} // namespace jazzer
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
new file mode 100644
index 00000000..dde8aaeb
--- /dev/null
+++ b/examples/BUILD.bazel
@@ -0,0 +1,321 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+load("@fmeum_rules_jni//jni:defs.bzl", "java_jni_library")
+load("//bazel:compat.bzl", "SKIP_ON_MACOS", "SKIP_ON_WINDOWS")
+load("//bazel:fuzz_target.bzl", "java_fuzz_target_test")
+
+java_fuzz_target_test(
+ name = "Autofuzz",
+ fuzzer_args = [
+ "--autofuzz=com.google.json.JsonSanitizer::sanitize",
+ # Exit after the first finding for testing purposes.
+ "--keep_going=1",
+ ],
+ runtime_deps = [
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "ExampleFuzzer",
+ srcs = [
+ "src/main/java/com/example/ExampleFuzzer.java",
+ "src/main/java/com/example/ExampleFuzzerHooks.java",
+ ],
+ # Comment out the next line to keep the fuzzer running indefinitely.
+ hook_classes = ["com.example.ExampleFuzzerHooks"],
+ target_class = "com.example.ExampleFuzzer",
+)
+
+java_jni_library(
+ name = "example_fuzzer_with_native_lib",
+ srcs = [
+ "src/main/java/com/example/ExampleFuzzerWithNative.java",
+ ],
+ native_libs = [
+ "//examples/src/main/native/com/example:native_asan",
+ "//examples/src/main/native/com/example:native_ubsan",
+ ],
+ visibility = ["//examples/src/main/native/com/example:__pkg__"],
+ deps = [
+ "//agent:jazzer_api_compile_only",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "ExampleFuzzerWithASan",
+ fuzzer_args = ["--jvm_args=-Djazzer.native_lib=native_asan"],
+ sanitizer = "address",
+ target_class = "com.example.ExampleFuzzerWithNative",
+ runtime_deps = [
+ ":example_fuzzer_with_native_lib",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "ExampleFuzzerWithUBSan",
+ fuzzer_args = ["--jvm_args=-Djazzer.native_lib=native_ubsan"],
+ sanitizer = "undefined",
+ target_class = "com.example.ExampleFuzzerWithNative",
+ # Crashes at runtime without an error message.
+ target_compatible_with = SKIP_ON_WINDOWS,
+ runtime_deps = [
+ ":example_fuzzer_with_native_lib",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "ExamplePathTraversalFuzzer",
+ srcs = [
+ "src/main/java/com/example/ExamplePathTraversalFuzzer.java",
+ "src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java",
+ ],
+ hook_classes = ["com.example.ExamplePathTraversalFuzzerHooks"],
+ target_class = "com.example.ExamplePathTraversalFuzzer",
+)
+
+java_fuzz_target_test(
+ name = "ExampleValueProfileFuzzer",
+ srcs = [
+ "src/main/java/com/example/ExampleValueProfileFuzzer.java",
+ ],
+ # Comment out the next line to keep the fuzzer running indefinitely.
+ fuzzer_args = ["-use_value_profile=1"],
+ target_class = "com.example.ExampleValueProfileFuzzer",
+)
+
+java_fuzz_target_test(
+ name = "ExampleOutOfMemoryFuzzer",
+ srcs = [
+ "src/main/java/com/example/ExampleOutOfMemoryFuzzer.java",
+ ],
+ fuzzer_args = ["--jvm_args=-Xmx512m"],
+ target_class = "com.example.ExampleOutOfMemoryFuzzer",
+)
+
+java_fuzz_target_test(
+ name = "ExampleStackOverflowFuzzer",
+ srcs = [
+ "src/main/java/com/example/ExampleStackOverflowFuzzer.java",
+ ],
+ target_class = "com.example.ExampleStackOverflowFuzzer",
+ # Crashes with a segfault before any stack trace printing is reached.
+ target_compatible_with = SKIP_ON_MACOS,
+)
+
+# WARNING: This fuzz target uses a vulnerable version of log4j, which could result in the execution
+# of arbitrary code during fuzzing if executed with an older JDK. Use at your own risk.
+java_fuzz_target_test(
+ name = "Log4jFuzzer",
+ timeout = "long",
+ srcs = [
+ "src/main/java/com/example/Log4jFuzzer.java",
+ ],
+ fuzzer_args = [
+ "-fork=4",
+ "-use_value_profile=1",
+ ],
+ # Finding this bug takes ~5 minutes on a decent laptop, but the GitHub Actions machines are not
+ # powerful enough to run it as part of our test suite.
+ tags = ["manual"],
+ target_class = "com.example.Log4jFuzzer",
+ deps = [
+ "@maven//:org_apache_logging_log4j_log4j_api",
+ "@maven//:org_apache_logging_log4j_log4j_core",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JpegImageParserFuzzer",
+ srcs = [
+ "src/main/java/com/example/JpegImageParserFuzzer.java",
+ ],
+ fuzzer_args = [
+ "-fork=5",
+ "--additional_jvm_args=-Dbaz=baz",
+ ] + select({
+ # \\\\ becomes \\ when evaluated as a Starlark string literal, then \ in
+ # java_fuzz_target_test.
+ "@platforms//os:windows": ["--jvm_args=-Dfoo=foo;-Dbar=b\\\\;ar"],
+ "//conditions:default": ["--jvm_args=-Dfoo=foo:-Dbar=b\\\\:ar"],
+ }),
+ target_class = "com.example.JpegImageParserFuzzer",
+ # The exit codes of the forked libFuzzer processes are not picked up correctly.
+ target_compatible_with = SKIP_ON_MACOS,
+ deps = [
+ "@maven//:org_apache_commons_commons_imaging",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "GifImageParserFuzzer",
+ srcs = [
+ "src/main/java/com/example/GifImageParserFuzzer.java",
+ ],
+ target_class = "com.example.GifImageParserFuzzer",
+ deps = [
+ "@maven//:org_apache_commons_commons_imaging",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "TiffImageParserFuzzer",
+ srcs = [
+ "src/main/java/com/example/TiffImageParserFuzzer.java",
+ ],
+ tags = ["manual"],
+ target_class = "com.example.TiffImageParserFuzzer",
+ deps = [
+ "@maven//:org_apache_commons_commons_imaging",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JsonSanitizerCrashFuzzer",
+ srcs = [
+ "src/main/java/com/example/JsonSanitizerCrashFuzzer.java",
+ ],
+ target_class = "com.example.JsonSanitizerCrashFuzzer",
+ deps = [
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JsonSanitizerDenylistFuzzer",
+ srcs = [
+ "src/main/java/com/example/JsonSanitizerDenylistFuzzer.java",
+ ],
+ target_class = "com.example.JsonSanitizerDenylistFuzzer",
+ deps = [
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+)
+
+java_binary(
+ name = "JsonSanitizerReplayerCrash",
+ data = [
+ ":json_sanitizer_denylist_crash",
+ ],
+ main_class = "com.code_intelligence.jazzer.replay.Replayer",
+ runtime_deps = [
+ ":JsonSanitizerDenylistFuzzer_target_deploy.jar",
+ "//agent/src/main/java/com/code_intelligence/jazzer/replay:Replayer_deploy.jar",
+ ],
+)
+
+sh_test(
+ name = "JsonSanitizerReplayerCrashTest",
+ srcs = ["check_for_finding.sh"],
+ args = [
+ "jazzer/$(rootpath :JsonSanitizerReplayerCrash)",
+ "com.example.JsonSanitizerDenylistFuzzer",
+ "jazzer/$(rootpath :json_sanitizer_denylist_crash)",
+ ],
+ data = [
+ ":JsonSanitizerReplayerCrash",
+ ":json_sanitizer_denylist_crash",
+ ],
+ deps = [
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JsonSanitizerIdempotenceFuzzer",
+ srcs = [
+ "src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java",
+ ],
+ target_class = "com.example.JsonSanitizerIdempotenceFuzzer",
+ deps = [
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JsonSanitizerValidJsonFuzzer",
+ srcs = [
+ "src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java",
+ ],
+ target_class = "com.example.JsonSanitizerValidJsonFuzzer",
+ deps = [
+ "@maven//:com_google_code_gson_gson",
+ "@maven//:com_mikesamuel_json_sanitizer",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "JacksonCborFuzzer",
+ srcs = [
+ "src/main/java/com/example/JacksonCborFuzzer.java",
+ ],
+ target_class = "com.example.JacksonCborFuzzer",
+ deps = [
+ "@maven//:com_fasterxml_jackson_core_jackson_core",
+ "@maven//:com_fasterxml_jackson_core_jackson_databind",
+ "@maven//:com_fasterxml_jackson_dataformat_jackson_dataformat_cbor",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "FastJsonFuzzer",
+ srcs = [
+ "src/main/java/com/example/FastJsonFuzzer.java",
+ ],
+ target_class = "com.example.FastJsonFuzzer",
+ deps = [
+ "@maven//:com_alibaba_fastjson",
+ ],
+)
+
+kt_jvm_library(
+ name = "KlaxonFuzzTarget",
+ srcs = [
+ "src/main/java/com/example/KlaxonFuzzer.kt",
+ ],
+ deps = [
+ "//agent:jazzer_api_compile_only",
+ "@maven//:com_beust_klaxon",
+ ],
+)
+
+java_fuzz_target_test(
+ name = "KlaxonFuzzer",
+ fuzzer_args = [
+ "--keep_going=7",
+ ],
+ target_class = "com.example.KlaxonFuzzer",
+ runtime_deps = [":KlaxonFuzzTarget"],
+)
+
+java_fuzz_target_test(
+ name = "TurboJpegFuzzer",
+ srcs = [
+ "src/main/java/com/example/TurboJpegFuzzer.java",
+ ],
+ fuzzer_args = [
+ "-rss_limit_mb=8196",
+ ],
+ native_libs = [
+ "@libjpeg_turbo//:turbojpeg_native",
+ ],
+ sanitizer = "address",
+ tags = ["manual"],
+ target_class = "com.example.TurboJpegFuzzer",
+ deps = [
+ "@libjpeg_turbo//:turbojpeg_java",
+ ],
+)
+
+java_binary(
+ name = "examples",
+ create_executable = False,
+ visibility = ["//visibility:public"],
+ runtime_deps = [
+ ":ExampleFuzzer_target_deploy.jar",
+ ":ExampleValueProfileFuzzer_target_deploy.jar",
+ ":FastJsonFuzzer_target_deploy.jar",
+ ":JacksonCborFuzzer_target_deploy.jar",
+ ":JpegImageParserFuzzer_target_deploy.jar",
+ ":JsonSanitizerDenylistFuzzer_target_deploy.jar",
+ ],
+)
diff --git a/examples/check_for_finding.sh b/examples/check_for_finding.sh
new file mode 100755
index 00000000..afe110c6
--- /dev/null
+++ b/examples/check_for_finding.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Copyright 2021 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.
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+# Temporarily disable exit on error since we expect the command below to fail.
+set +e
+"$(rlocation "$1")" "$2" "$(rlocation "$3")" "${@:4}"
+declare -i exit_code=$?
+set -e
+
+# Assert that we either found a crash in java (exit code 77) or an ASan crash
+# (exit code 76).
+if [ $exit_code -eq 77 ] || [ $exit_code -eq 76 ]
+then
+ exit 0
+else
+ echo "Unexpected exit code: $exit_code"
+ exit 1
+fi
diff --git a/examples/json_sanitizer_denylist_crash b/examples/json_sanitizer_denylist_crash
new file mode 100644
index 00000000..7324203a
--- /dev/null
+++ b/examples/json_sanitizer_denylist_crash
Binary files differ
diff --git a/examples/src/main/java/com/example/ExampleFuzzer.java b/examples/src/main/java/com/example/ExampleFuzzer.java
new file mode 100644
index 00000000..073d924a
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleFuzzer.java
@@ -0,0 +1,40 @@
+// Copyright 2021 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.api.FuzzerSecurityIssueMedium;
+import java.security.SecureRandom;
+
+public class ExampleFuzzer {
+ public static void fuzzerInitialize() {
+ // Optional initialization to be run before the first call to fuzzerTestOneInput.
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ // Without the hook in ExampleFuzzerHooks.java, the value of random would change on every
+ // invocation, making it almost impossible to guess for the fuzzer.
+ long random = new SecureRandom().nextLong();
+ if (input.startsWith("magicstring" + random) && input.length() > 30
+ && input.charAt(25) == 'C') {
+ mustNeverBeCalled();
+ }
+ }
+
+ private static void mustNeverBeCalled() {
+ throw new FuzzerSecurityIssueMedium("mustNeverBeCalled has been called");
+ }
+}
diff --git a/examples/src/main/java/com/example/ExampleFuzzerHooks.java b/examples/src/main/java/com/example/ExampleFuzzerHooks.java
new file mode 100644
index 00000000..41f16635
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleFuzzerHooks.java
@@ -0,0 +1,30 @@
+// Copyright 2021 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.HookType;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+
+public class ExampleFuzzerHooks {
+ @MethodHook(type = HookType.REPLACE, targetClassName = "java.security.SecureRandom",
+ targetMethod = "nextLong", targetMethodDescriptor = "()J")
+ public static long
+ getRandomNumber(MethodHandle handle, Object thisObject, Object[] args, int hookId) {
+ return 4; // chosen by fair dice roll.
+ // guaranteed to be random.
+ // https://xkcd.com/221/
+ }
+}
diff --git a/examples/src/main/java/com/example/ExampleFuzzerWithNative.java b/examples/src/main/java/com/example/ExampleFuzzerWithNative.java
new file mode 100644
index 00000000..b9a13e24
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleFuzzerWithNative.java
@@ -0,0 +1,36 @@
+// Copyright 2021 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.github.fmeum.rules_jni.RulesJni;
+
+public class ExampleFuzzerWithNative {
+ static {
+ String native_lib = System.getProperty("jazzer.native_lib");
+ RulesJni.loadLibrary(native_lib, ExampleFuzzerWithNative.class);
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ int val = data.consumeInt();
+ String stringData = data.consumeRemainingAsString();
+ if (val == 17759716 && stringData.length() > 10 && stringData.contains("jazzer")) {
+ // call native function which contains a crash
+ new ExampleFuzzerWithNative().parse(stringData);
+ }
+ }
+
+ private native boolean parse(String bytes);
+} \ No newline at end of file
diff --git a/examples/src/main/java/com/example/ExampleOutOfMemoryFuzzer.java b/examples/src/main/java/com/example/ExampleOutOfMemoryFuzzer.java
new file mode 100644
index 00000000..d704da39
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleOutOfMemoryFuzzer.java
@@ -0,0 +1,28 @@
+// Copyright 2021 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 java.util.ArrayList;
+
+public class ExampleOutOfMemoryFuzzer {
+ public static void fuzzerTestOneInput(byte[] input) {
+ ArrayList<Byte> bytes = new ArrayList<>();
+ int pos = 0;
+ while (pos >= 0 && pos < input.length) {
+ bytes.add(input[pos]);
+ pos += input[pos] + 1;
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/ExamplePathTraversalFuzzer.java b/examples/src/main/java/com/example/ExamplePathTraversalFuzzer.java
new file mode 100644
index 00000000..f53ccb3c
--- /dev/null
+++ b/examples/src/main/java/com/example/ExamplePathTraversalFuzzer.java
@@ -0,0 +1,43 @@
+// Copyright 2021 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 java.io.File;
+import java.io.IOException;
+
+public class ExamplePathTraversalFuzzer {
+ /**
+ * The root path for all files that this application is allowed to upload.
+ */
+ public static final String publicFilesRootPath = "/app/upload/";
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String relativePath = data.consumeRemainingAsAsciiString();
+ // Upload the file and try very hard to ignore errors thrown during the upload.
+ try {
+ uploadFile(relativePath);
+ } catch (Throwable ignored) {
+ }
+ }
+
+ private static void uploadFile(String relativePathToFile) throws IOException {
+ File fileToUpload = new File(publicFilesRootPath + relativePathToFile);
+ if (!fileToUpload.exists()) {
+ throw new IOException("File not found");
+ }
+ // In a real application, the file would be uploaded to a public server here.
+ }
+}
diff --git a/examples/src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java b/examples/src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java
new file mode 100644
index 00000000..b027de5b
--- /dev/null
+++ b/examples/src/main/java/com/example/ExamplePathTraversalFuzzerHooks.java
@@ -0,0 +1,46 @@
+// Copyright 2021 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.FuzzerSecurityIssueHigh;
+import com.code_intelligence.jazzer.api.HookType;
+import com.code_intelligence.jazzer.api.Jazzer;
+import com.code_intelligence.jazzer.api.MethodHook;
+import java.lang.invoke.MethodHandle;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ExamplePathTraversalFuzzerHooks {
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.io.File", targetMethod = "<init>",
+ targetMethodDescriptor = "(Ljava/lang/String;)V")
+ public static void
+ fileConstructorHook(MethodHandle handle, Object thisObject, Object[] args, int hookId) {
+ String path = (String) args[0];
+ Path normalizedPath;
+ try {
+ normalizedPath = Paths.get(path).normalize();
+ } catch (InvalidPathException e) {
+ // Invalid paths are correctly rejected by the application.
+ return;
+ }
+ if (!normalizedPath.startsWith(ExamplePathTraversalFuzzer.publicFilesRootPath)) {
+ // Simply throwing an exception from here would not work as the calling code catches and
+ // ignores all Throwables. Instead, use the Jazzer API to report a finding from a hook.
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh(
+ "Path traversal discovered: '" + path + "' --> '" + normalizedPath + "'"));
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/ExampleStackOverflowFuzzer.java b/examples/src/main/java/com/example/ExampleStackOverflowFuzzer.java
new file mode 100644
index 00000000..47166af4
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleStackOverflowFuzzer.java
@@ -0,0 +1,33 @@
+// Copyright 2021 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 java.math.BigDecimal;
+
+public class ExampleStackOverflowFuzzer {
+ public static void fuzzerTestOneInput(byte[] input) {
+ step1();
+ }
+
+ private static void step1() {
+ BigDecimal unused = BigDecimal.valueOf(10, 100);
+ step2();
+ }
+
+ private static void step2() {
+ boolean unused = "foobar".contains("bar");
+ step1();
+ }
+}
diff --git a/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java
new file mode 100644
index 00000000..acc023a2
--- /dev/null
+++ b/examples/src/main/java/com/example/ExampleValueProfileFuzzer.java
@@ -0,0 +1,53 @@
+// Copyright 2021 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.api.FuzzerSecurityIssueLow;
+import java.util.Base64;
+
+public class ExampleValueProfileFuzzer {
+ private static String base64(byte[] input) {
+ return Base64.getEncoder().encodeToString(input);
+ }
+
+ private static long insecureEncrypt(long input) {
+ long key = 0xefe4eb93215cb6b0L;
+ return input ^ key;
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ // Without -use_value_profile=1, the fuzzer gets stuck here as there is no direct correspondence
+ // between the input bytes and the compared string. With value profile, the fuzzer can guess the
+ // expected input byte by byte, which takes linear rather than exponential time.
+ if (base64(data.consumeBytes(6)).equals("SmF6emVy")) {
+ long[] plaintextBlocks = data.consumeLongs(2);
+ if (plaintextBlocks.length != 2)
+ return;
+ if (insecureEncrypt(plaintextBlocks[0]) == 0x9fc48ee64d3dc090L) {
+ // Without --fake_pcs (enabled by default with -use_value_profile=1), the fuzzer would get
+ // stuck here as the value profile information for long comparisons would not be able to
+ // distinguish between this comparison and the one above.
+ if (insecureEncrypt(plaintextBlocks[1]) == 0x888a82ff483ad9c2L) {
+ mustNeverBeCalled();
+ }
+ }
+ }
+ }
+
+ private static void mustNeverBeCalled() {
+ throw new FuzzerSecurityIssueLow("mustNeverBeCalled has been called");
+ }
+}
diff --git a/examples/src/main/java/com/example/FastJsonFuzzer.java b/examples/src/main/java/com/example/FastJsonFuzzer.java
new file mode 100644
index 00000000..2e5d4797
--- /dev/null
+++ b/examples/src/main/java/com/example/FastJsonFuzzer.java
@@ -0,0 +1,30 @@
+// Copyright 2021 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.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONException;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+// Found the issues described in
+// https://github.com/alibaba/fastjson/issues/3631
+public class FastJsonFuzzer {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ try {
+ JSON.parse(data.consumeRemainingAsString());
+ } catch (JSONException ignored) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/GifImageParserFuzzer.java b/examples/src/main/java/com/example/GifImageParserFuzzer.java
new file mode 100644
index 00000000..ab7de907
--- /dev/null
+++ b/examples/src/main/java/com/example/GifImageParserFuzzer.java
@@ -0,0 +1,32 @@
+// Copyright 2021 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 java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
+import org.apache.commons.imaging.formats.gif.GifImageParser;
+
+// Found https://issues.apache.org/jira/browse/IMAGING-277 and
+// https://issues.apache.org/jira/browse/IMAGING-278.
+public class GifImageParserFuzzer {
+ public static void fuzzerTestOneInput(byte[] input) {
+ try {
+ new GifImageParser().getBufferedImage(new ByteSourceArray(input), new HashMap<>());
+ } catch (IOException | ImageReadException ignored) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/JacksonCborFuzzer.java b/examples/src/main/java/com/example/JacksonCborFuzzer.java
new file mode 100644
index 00000000..902c1d96
--- /dev/null
+++ b/examples/src/main/java/com/example/JacksonCborFuzzer.java
@@ -0,0 +1,34 @@
+// Copyright 2021 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.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
+import java.io.IOException;
+
+// Reproduces https://github.com/FasterXML/jackson-dataformats-binary/issues/236 and
+// https://github.com/FasterXML/jackson-databind/pull/3032 if executed with
+// `--keep_going=3 -seed=2735196724`.
+public class JacksonCborFuzzer {
+ public static void fuzzerTestOneInput(byte[] input) {
+ CBORFactory factory = new CBORFactory();
+ ObjectMapper mapper = new ObjectMapper(factory);
+ mapper.enableDefaultTyping();
+ try {
+ mapper.readTree(input);
+ } catch (IOException ignored) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/JpegImageParserFuzzer.java b/examples/src/main/java/com/example/JpegImageParserFuzzer.java
new file mode 100644
index 00000000..a6898bf0
--- /dev/null
+++ b/examples/src/main/java/com/example/JpegImageParserFuzzer.java
@@ -0,0 +1,45 @@
+// Copyright 2021 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 java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
+import org.apache.commons.imaging.formats.jpeg.JpegImageParser;
+
+// Found https://issues.apache.org/jira/browse/IMAGING-275.
+public class JpegImageParserFuzzer {
+ public static void fuzzerInitialize() {
+ String foo = System.getProperty("foo");
+ String bar = System.getProperty("bar");
+ String baz = System.getProperty("baz");
+ // Only used to verify that arguments are correctly passed down to child processes.
+ if (foo == null || bar == null || baz == null || !foo.equals("foo")
+ || !(bar.equals("b;ar") || bar.equals("b:ar")) || !baz.equals("baz")) {
+ // Exit the process with an exit code different from that for a finding.
+ System.err.println("ERROR: Did not correctly pass all jvm_args to child process.");
+ System.err.printf("foo: %s%nbar: %s%nbaz: %s%n", foo, bar, baz);
+ System.exit(3);
+ }
+ }
+
+ public static void fuzzerTestOneInput(byte[] input) {
+ try {
+ new JpegImageParser().getBufferedImage(new ByteSourceArray(input), new HashMap<>());
+ } catch (IOException | ImageReadException ignored) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/JsonSanitizerCrashFuzzer.java b/examples/src/main/java/com/example/JsonSanitizerCrashFuzzer.java
new file mode 100644
index 00000000..05ac4611
--- /dev/null
+++ b/examples/src/main/java/com/example/JsonSanitizerCrashFuzzer.java
@@ -0,0 +1,30 @@
+// Copyright 2021 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.google.json.JsonSanitizer;
+
+public class JsonSanitizerCrashFuzzer {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ try {
+ JsonSanitizer.sanitize(input, 10);
+ } catch (ArrayIndexOutOfBoundsException ignored) {
+ // ArrayIndexOutOfBoundsException is expected if nesting depth is
+ // exceeded.
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/JsonSanitizerDenylistFuzzer.java b/examples/src/main/java/com/example/JsonSanitizerDenylistFuzzer.java
new file mode 100644
index 00000000..e715b1d9
--- /dev/null
+++ b/examples/src/main/java/com/example/JsonSanitizerDenylistFuzzer.java
@@ -0,0 +1,45 @@
+// Copyright 2021 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.api.FuzzerSecurityIssueHigh;
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
+import com.google.json.JsonSanitizer;
+
+public class JsonSanitizerDenylistFuzzer {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ String validJson;
+ try {
+ validJson = JsonSanitizer.sanitize(input, 10);
+ } catch (Exception e) {
+ return;
+ }
+
+ // Check for forbidden substrings. As these would enable Cross-Site Scripting, treat every
+ // finding as a high severity vulnerability.
+ assert !validJson.contains("</script")
+ : new FuzzerSecurityIssueHigh("Output contains </script");
+ assert !validJson.contains("]]>") : new FuzzerSecurityIssueHigh("Output contains ]]>");
+
+ // Check for more forbidden substrings. As these would not directly enable Cross-Site Scripting
+ // in general, but may impact script execution on the embedding page, treat each finding as a
+ // medium severity vulnerability.
+ assert !validJson.contains("<script")
+ : new FuzzerSecurityIssueMedium("Output contains <script");
+ assert !validJson.contains("<!--") : new FuzzerSecurityIssueMedium("Output contains <!--");
+ }
+}
diff --git a/examples/src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java b/examples/src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java
new file mode 100644
index 00000000..111d3de2
--- /dev/null
+++ b/examples/src/main/java/com/example/JsonSanitizerIdempotenceFuzzer.java
@@ -0,0 +1,34 @@
+// Copyright 2021 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.google.json.JsonSanitizer;
+
+public class JsonSanitizerIdempotenceFuzzer {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ String validJson;
+ try {
+ validJson = JsonSanitizer.sanitize(input, 10);
+ } catch (Exception e) {
+ return;
+ }
+
+ // Ensure that sanitizing twice does not give different output (idempotence). Since failure to
+ // be idempotent is not a security issue in itself, fail with a regular AssertionError.
+ assert JsonSanitizer.sanitize(validJson).equals(validJson) : "Not idempotent";
+ }
+}
diff --git a/examples/src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java b/examples/src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java
new file mode 100644
index 00000000..2d270966
--- /dev/null
+++ b/examples/src/main/java/com/example/JsonSanitizerValidJsonFuzzer.java
@@ -0,0 +1,42 @@
+// Copyright 2021 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.api.FuzzerSecurityIssueLow;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.json.JsonSanitizer;
+
+public class JsonSanitizerValidJsonFuzzer {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsString();
+ String validJson;
+ try {
+ validJson = JsonSanitizer.sanitize(input, 10);
+ } catch (Exception e) {
+ return;
+ }
+
+ // Check that the output is valid JSON. Invalid JSON may crash other parts of the application
+ // that trust the output of the sanitizer.
+ try {
+ Gson gson = new Gson();
+ gson.fromJson(validJson, JsonElement.class);
+ } catch (Exception e) {
+ throw new FuzzerSecurityIssueLow("Output is invalid JSON", e);
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/KlaxonFuzzer.kt b/examples/src/main/java/com/example/KlaxonFuzzer.kt
new file mode 100644
index 00000000..39216eea
--- /dev/null
+++ b/examples/src/main/java/com/example/KlaxonFuzzer.kt
@@ -0,0 +1,31 @@
+// Copyright 2021 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.beust.klaxon.KlaxonException
+import com.beust.klaxon.Parser
+import com.code_intelligence.jazzer.api.FuzzedDataProvider
+
+// Reproduces https://github.com/cbeust/klaxon/pull/330
+object KlaxonFuzzer {
+
+ @JvmStatic
+ fun fuzzerTestOneInput(data: FuzzedDataProvider) {
+ try {
+ Parser.default().parse(StringBuilder(data.consumeRemainingAsString()))
+ } catch (_: KlaxonException) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/Log4jFuzzer.java b/examples/src/main/java/com/example/Log4jFuzzer.java
new file mode 100644
index 00000000..41870c9c
--- /dev/null
+++ b/examples/src/main/java/com/example/Log4jFuzzer.java
@@ -0,0 +1,82 @@
+// Copyright 2021 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 org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+
+// This fuzzer reproduces the log4j RCE vulnerability CVE-2021-44228.
+public class Log4jFuzzer {
+ private final static Logger log = LogManager.getLogger(Log4jFuzzer.class.getName());
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ log.error(data.consumeRemainingAsString());
+ }
+
+ public static void fuzzerInitialize() {
+ // Install a logger that constructs the log message, but never prints it.
+ // This noticeably increases the fuzzing performance
+ DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder();
+ configBuilder.setPackages(FuzzingAppender.class.getPackage().getName());
+ AppenderComponentBuilder fuzzingAppender =
+ configBuilder.newAppender("nullAppender", "FuzzingAppender");
+ configBuilder.add(fuzzingAppender);
+ RootLoggerComponentBuilder rootLogger = configBuilder.newRootLogger();
+ rootLogger.add(configBuilder.newAppenderRef("nullAppender"));
+ configBuilder.add(rootLogger);
+ Configurator.reconfigure(configBuilder.build());
+
+ // Disable logging of exceptions caught in log4j itself.
+ StatusLogger.getLogger().reset();
+ StatusLogger.getLogger().setLevel(Level.OFF);
+ }
+
+ @Plugin(
+ name = "FuzzingAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
+ public static class FuzzingAppender extends AbstractAppender {
+ protected FuzzingAppender(String name) {
+ super(name, null, PatternLayout.createDefaultLayout(), true);
+ }
+
+ @PluginFactory
+ public static FuzzingAppender createAppender(@PluginAttribute("name") String name) {
+ return new FuzzingAppender(name);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ try {
+ getLayout().toByteArray(event);
+ } catch (Exception ignored) {
+ // Prevent exceptions from being logged to stderr.
+ }
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/TiffImageParserFuzzer.java b/examples/src/main/java/com/example/TiffImageParserFuzzer.java
new file mode 100644
index 00000000..5fa1af2d
--- /dev/null
+++ b/examples/src/main/java/com/example/TiffImageParserFuzzer.java
@@ -0,0 +1,31 @@
+// Copyright 2021 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 java.io.IOException;
+import java.util.HashMap;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
+import org.apache.commons.imaging.formats.tiff.TiffImageParser;
+
+// Found https://issues.apache.org/jira/browse/IMAGING-276.
+public class TiffImageParserFuzzer {
+ public static void fuzzerTestOneInput(byte[] input) {
+ try {
+ new TiffImageParser().getBufferedImage(new ByteSourceArray(input), new HashMap<>());
+ } catch (IOException | ImageReadException ignored) {
+ }
+ }
+}
diff --git a/examples/src/main/java/com/example/TurboJpegFuzzer.java b/examples/src/main/java/com/example/TurboJpegFuzzer.java
new file mode 100644
index 00000000..b9ea715b
--- /dev/null
+++ b/examples/src/main/java/com/example/TurboJpegFuzzer.java
@@ -0,0 +1,59 @@
+// Copyright 2021 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 org.libjpegturbo.turbojpeg.TJ;
+import org.libjpegturbo.turbojpeg.TJDecompressor;
+import org.libjpegturbo.turbojpeg.TJException;
+import org.libjpegturbo.turbojpeg.TJTransform;
+import org.libjpegturbo.turbojpeg.TJTransformer;
+
+public class TurboJpegFuzzer {
+ static byte[] buffer = new byte[128 * 128 * 4];
+
+ public static void fuzzerInitialize() throws TJException {
+ // Trigger an early load of the native library to show the coverage counters stats in libFuzzer.
+ new TJDecompressor();
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ try {
+ int flagsDecompress = data.consumeInt();
+ int flagsTransform = data.consumeInt();
+ int pixelFormat = data.consumeInt(TJ.PF_RGB, TJ.PF_CMYK);
+ // Specify explicit small target width/height so that we can reuse a
+ // fixed-size buffer.
+ int desiredWidth = data.consumeInt(1, 128);
+ int desiredHeight = data.consumeInt(1, 128);
+ int transformOp = data.consumeInt(TJTransform.OP_NONE, TJTransform.OP_ROT270);
+ int transformOptions = data.consumeInt();
+ int transformWidth = data.consumeBoolean() ? 128 : 64;
+ int transformHeight = data.consumeBoolean() ? 128 : 64;
+ TJDecompressor tjd;
+ if (data.consumeBoolean()) {
+ TJTransformer tjt = new TJTransformer(data.consumeRemainingAsBytes());
+ TJTransform tjf = new TJTransform(
+ 0, 0, transformWidth, transformHeight, transformOp, transformOptions, null);
+ tjd = tjt.transform(new TJTransform[] {tjf}, flagsTransform)[0];
+ } else {
+ tjd = new TJDecompressor(data.consumeRemainingAsBytes());
+ }
+ tjd.decompress(buffer, 0, 0, desiredWidth, 0, desiredHeight, pixelFormat, flagsDecompress);
+ } catch (Exception ignored) {
+ // We are not looking for Java exceptions, but segfaults and ASan reports.
+ }
+ }
+}
diff --git a/examples/src/main/native/com/example/BUILD.bazel b/examples/src/main/native/com/example/BUILD.bazel
new file mode 100644
index 00000000..7f23f75e
--- /dev/null
+++ b/examples/src/main/native/com/example/BUILD.bazel
@@ -0,0 +1,49 @@
+load("@fmeum_rules_jni//jni:defs.bzl", "cc_jni_library")
+
+cc_jni_library(
+ name = "native_asan",
+ srcs = [
+ "com_example_ExampleFuzzerWithNative.cpp",
+ ],
+ copts = [
+ "-fsanitize=fuzzer-no-link,address",
+ "-fno-sanitize-blacklist",
+ ],
+ linkopts = select({
+ "//:clang_on_linux": ["-fuse-ld=lld"],
+ "@platforms//os:windows": [
+ # Windows requires all symbols that should be imported from the main
+ # executable to be defined by an import lib.
+ "/wholearchive:clang_rt.asan_dll_thunk-x86_64.lib",
+ ],
+ "//conditions:default": [],
+ }),
+ visibility = ["//examples:__pkg__"],
+ deps = [
+ "//examples:example_fuzzer_with_native_lib.hdrs",
+ ],
+)
+
+cc_jni_library(
+ name = "native_ubsan",
+ srcs = [
+ "com_example_ExampleFuzzerWithNative.cpp",
+ ],
+ copts = [
+ "-fsanitize=fuzzer-no-link,undefined",
+ "-fno-sanitize-recover=all",
+ ],
+ linkopts = select({
+ "//:clang_on_linux": ["-fuse-ld=lld"],
+ "@platforms//os:windows": [
+ # Using the asan thunk is correct here as it contains symbols for
+ # UBSan and SanCov as well.
+ "/wholearchive:clang_rt.asan_dll_thunk-x86_64.lib",
+ ],
+ "//conditions:default": [],
+ }),
+ visibility = ["//examples:__pkg__"],
+ deps = [
+ "//examples:example_fuzzer_with_native_lib.hdrs",
+ ],
+)
diff --git a/examples/src/main/native/com/example/com_example_ExampleFuzzerWithNative.cpp b/examples/src/main/native/com/example/com_example_ExampleFuzzerWithNative.cpp
new file mode 100644
index 00000000..774e5998
--- /dev/null
+++ b/examples/src/main/native/com/example/com_example_ExampleFuzzerWithNative.cpp
@@ -0,0 +1,42 @@
+// Copyright 2021 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.
+
+#include "com_example_ExampleFuzzerWithNative.h"
+
+#include <limits>
+#include <string>
+
+// simple function containing a crash that requires coverage and string compare
+// instrumentation for the fuzzer to find
+__attribute__((optnone)) void parseInternal(const std::string &input) {
+ constexpr int bar = std::numeric_limits<int>::max() - 5;
+ // Crashes with UBSan.
+ if (bar + input[0] == 300) {
+ return;
+ }
+ if (input[0] == 'a' && input[1] == 'b' && input[5] == 'c') {
+ if (input.find("secret_in_native_library") != std::string::npos) {
+ // Crashes with ASan.
+ [[maybe_unused]] char foo = input[input.size() + 2];
+ }
+ }
+}
+
+JNIEXPORT jboolean JNICALL Java_com_example_ExampleFuzzerWithNative_parse(
+ JNIEnv *env, jobject o, jstring bytes) {
+ const char *input(env->GetStringUTFChars(bytes, nullptr));
+ parseInternal(input);
+ env->ReleaseStringUTFChars(bytes, input);
+ return false;
+}
diff --git a/format.sh b/format.sh
new file mode 100755
index 00000000..f783f6e9
--- /dev/null
+++ b/format.sh
@@ -0,0 +1,14 @@
+# C++ & Java
+find -name '*.cpp' -o -name '*.h' -o -name '*.java' | xargs clang-format-13 -i
+
+# Kotlin
+# curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.42.1/ktlint && chmod a+x ktlint
+ktlint -F "agent/**/*.kt" "driver/**/*.kt" "examples/**/*.kt" "sanitizers/**/*.kt"
+
+# BUILD files
+# go get github.com/bazelbuild/buildtools/buildifier
+buildifier -r .
+
+# Licence headers
+# go get -u github.com/google/addlicense
+addlicense -c "Code Intelligence GmbH" agent/ bazel/ deploy/ docker/ driver/ examples/ sanitizers/ *.bzl
diff --git a/init.bzl b/init.bzl
new file mode 100644
index 00000000..4e2a25c6
--- /dev/null
+++ b/init.bzl
@@ -0,0 +1,29 @@
+# Copyright 2021 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.
+
+"""Dependency initialization utilities."""
+
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar_repositories")
+load("@io_bazel_rules_kotlin//kotlin:dependencies.bzl", "kt_download_local_dev_dependencies")
+load("@io_bazel_rules_kotlin//kotlin:repositories.bzl", "kotlin_repositories")
+load("@fmeum_rules_jni//jni:repositories.bzl", "rules_jni_dependencies")
+
+def jazzer_init():
+ bazel_skylib_workspace()
+ kt_download_local_dev_dependencies()
+ kotlin_repositories()
+ native.register_toolchains("@jazzer//:kotlin_toolchain")
+ jar_jar_repositories()
+ rules_jni_dependencies()
diff --git a/jazzer-api.pom b/jazzer-api.pom
new file mode 100644
index 00000000..ef413bba
--- /dev/null
+++ b/jazzer-api.pom
@@ -0,0 +1,38 @@
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>{groupId}</groupId>
+ <artifactId>{artifactId}</artifactId>
+ <version>{version}</version>
+ <packaging>jar</packaging>
+ {dependencies}
+
+ <name>Jazzer API</name>
+ <description>Helper functions and annotations for Jazzer fuzz targets</description>
+ <url>https://github.com/CodeIntelligenceTesting/jazzer</url>
+
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <organization>
+ <name>Code Intelligence GmbH</name>
+ <url>https://code-intelligence.com</url>
+ </organization>
+
+ <developers>
+ <developer>
+ <id>fmeum</id>
+ <name>Fabian Meumertzheim</name>
+ <email>meumertzheim@code-intelligence.com</email>
+ <organization>Code Intelligence GmbH</organization>
+ </developer>
+ </developers>
+
+ <scm>
+ <url>https://github.com/CodeIntelligenceTesting/jazzer</url>
+ </scm>
+</project> \ No newline at end of file
diff --git a/maven.bzl b/maven.bzl
new file mode 100644
index 00000000..29f38451
--- /dev/null
+++ b/maven.bzl
@@ -0,0 +1,38 @@
+# Copyright 2021 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.
+
+load("@rules_jvm_external//:specs.bzl", "maven")
+
+JAZZER_API_VERSION = "0.10.0"
+JAZZER_API_COORDINATES = "com.code-intelligence:jazzer-api:%s" % JAZZER_API_VERSION
+
+# **WARNING**: These Maven dependencies have known vulnerabilities and are only used to test that
+# Jazzer finds these issues. DO NOT USE.
+MAVEN_ARTIFACTS = [
+ "junit:junit:4.12",
+ "org.apache.commons:commons-imaging:1.0-alpha2",
+ "com.mikesamuel:json-sanitizer:1.2.1",
+ "com.google.code.gson:gson:2.8.6",
+ "com.fasterxml.jackson.core:jackson-core:2.12.1",
+ "com.fasterxml.jackson.core:jackson-databind:2.12.1",
+ "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.1",
+ "com.alibaba:fastjson:1.2.75",
+ "com.beust:klaxon:5.5",
+ "javax.validation:validation-api:2.0.1.Final",
+ "javax.xml.bind:jaxb-api:2.3.1",
+ "javax.el:javax.el-api:3.0.1-b06",
+ "org.hibernate:hibernate-validator:5.2.4.Final",
+ maven.artifact("org.apache.logging.log4j", "log4j-api", "2.14.1", testonly = True),
+ maven.artifact("org.apache.logging.log4j", "log4j-core", "2.14.1", testonly = True),
+]
diff --git a/maven_install.json b/maven_install.json
new file mode 100644
index 00000000..16303c03
--- /dev/null
+++ b/maven_install.json
@@ -0,0 +1,328 @@
+{
+ "dependency_tree": {
+ "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
+ "__INPUT_ARTIFACTS_HASH": 993215468,
+ "__RESOLVED_ARTIFACTS_HASH": 40706841,
+ "conflict_resolution": {},
+ "dependencies": [
+ {
+ "coord": "com.alibaba:fastjson:1.2.75",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar"
+ ],
+ "sha256": "9ba58edc4473ee813eca9b8dd4a066378023b7b7e2e605823c07a16abfade189",
+ "url": "https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar"
+ },
+ {
+ "coord": "com.beust:klaxon:5.5",
+ "dependencies": [
+ "org.jetbrains.kotlin:kotlin-stdlib:1.4.31",
+ "org.jetbrains:annotations:13.0",
+ "org.jetbrains.kotlin:kotlin-reflect:1.4.31",
+ "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.31"
+ ],
+ "directDependencies": [
+ "org.jetbrains.kotlin:kotlin-reflect:1.4.31",
+ "org.jetbrains.kotlin:kotlin-stdlib:1.4.31"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/beust/klaxon/5.5/klaxon-5.5.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/beust/klaxon/5.5/klaxon-5.5.jar"
+ ],
+ "sha256": "7f70ecba1cdce3d0cea5c94eaae19e6d0c5a82181b6c24d3dd808a3419e83663",
+ "url": "https://repo1.maven.org/maven2/com/beust/klaxon/5.5/klaxon-5.5.jar"
+ },
+ {
+ "coord": "com.fasterxml.jackson.core:jackson-annotations:2.12.1",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.1/jackson-annotations-2.12.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.1/jackson-annotations-2.12.1.jar"
+ ],
+ "sha256": "203cefdfa6c81e6aa84e11f292f29ca97344a3c3bc0293abea065cd837592873",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.12.1/jackson-annotations-2.12.1.jar"
+ },
+ {
+ "coord": "com.fasterxml.jackson.core:jackson-core:2.12.1",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.1/jackson-core-2.12.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.1/jackson-core-2.12.1.jar"
+ ],
+ "sha256": "cc899cb6eae0c80b87d590eea86528797369cc4feb7b79463207d6bb18f0c257",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.12.1/jackson-core-2.12.1.jar"
+ },
+ {
+ "coord": "com.fasterxml.jackson.core:jackson-databind:2.12.1",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-annotations:2.12.1",
+ "com.fasterxml.jackson.core:jackson-core:2.12.1"
+ ],
+ "directDependencies": [
+ "com.fasterxml.jackson.core:jackson-annotations:2.12.1",
+ "com.fasterxml.jackson.core:jackson-core:2.12.1"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.1/jackson-databind-2.12.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.1/jackson-databind-2.12.1.jar"
+ ],
+ "sha256": "f2ca3c28ebded59c98447d51afe945323df961540af66a063c015597af936aa0",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.12.1/jackson-databind-2.12.1.jar"
+ },
+ {
+ "coord": "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.1",
+ "dependencies": [
+ "com.fasterxml.jackson.core:jackson-annotations:2.12.1",
+ "com.fasterxml.jackson.core:jackson-databind:2.12.1",
+ "com.fasterxml.jackson.core:jackson-core:2.12.1"
+ ],
+ "directDependencies": [
+ "com.fasterxml.jackson.core:jackson-core:2.12.1",
+ "com.fasterxml.jackson.core:jackson-databind:2.12.1"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.12.1/jackson-dataformat-cbor-2.12.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.12.1/jackson-dataformat-cbor-2.12.1.jar"
+ ],
+ "sha256": "e76779aea9427ca73d7f407e5fa808a0405578ec056653a865c26e4c6f01d428",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.12.1/jackson-dataformat-cbor-2.12.1.jar"
+ },
+ {
+ "coord": "com.fasterxml:classmate:1.1.0",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar"
+ ],
+ "sha256": "610d23db8ece7268e93930562d89b91546c79fc80f3966baf433e5e93110b118",
+ "url": "https://repo1.maven.org/maven2/com/fasterxml/classmate/1.1.0/classmate-1.1.0.jar"
+ },
+ {
+ "coord": "com.google.code.gson:gson:2.8.6",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar"
+ ],
+ "sha256": "c8fb4839054d280b3033f800d1f5a97de2f028eb8ba2eb458ad287e536f3f25f",
+ "url": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar"
+ },
+ {
+ "coord": "com.mikesamuel:json-sanitizer:1.2.1",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/com/mikesamuel/json-sanitizer/1.2.1/json-sanitizer-1.2.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/com/mikesamuel/json-sanitizer/1.2.1/json-sanitizer-1.2.1.jar"
+ ],
+ "sha256": "0f7d702ba2cdfebac1d4f1154d4b107f508d5920c268263087a5f4b80ddb7446",
+ "url": "https://repo1.maven.org/maven2/com/mikesamuel/json-sanitizer/1.2.1/json-sanitizer-1.2.1.jar"
+ },
+ {
+ "coord": "javax.activation:javax.activation-api:1.2.0",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar"
+ ],
+ "sha256": "43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393",
+ "url": "https://repo1.maven.org/maven2/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar"
+ },
+ {
+ "coord": "javax.el:javax.el-api:3.0.1-b06",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar"
+ ],
+ "sha256": "0b46b36709ecbb9791ac4ba44d16125b9d65b576112afdaaa286052b6e498bc4",
+ "url": "https://repo1.maven.org/maven2/javax/el/javax.el-api/3.0.1-b06/javax.el-api-3.0.1-b06.jar"
+ },
+ {
+ "coord": "javax.validation:validation-api:2.0.1.Final",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar"
+ ],
+ "sha256": "9873b46df1833c9ee8f5bc1ff6853375115dadd8897bcb5a0dffb5848835ee6c",
+ "url": "https://repo1.maven.org/maven2/javax/validation/validation-api/2.0.1.Final/validation-api-2.0.1.Final.jar"
+ },
+ {
+ "coord": "javax.xml.bind:jaxb-api:2.3.1",
+ "dependencies": [
+ "javax.activation:javax.activation-api:1.2.0"
+ ],
+ "directDependencies": [
+ "javax.activation:javax.activation-api:1.2.0"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar"
+ ],
+ "sha256": "88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06",
+ "url": "https://repo1.maven.org/maven2/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar"
+ },
+ {
+ "coord": "junit:junit:4.12",
+ "dependencies": [
+ "org.hamcrest:hamcrest-core:1.3"
+ ],
+ "directDependencies": [
+ "org.hamcrest:hamcrest-core:1.3"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar"
+ ],
+ "sha256": "59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a",
+ "url": "https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar"
+ },
+ {
+ "coord": "org.apache.commons:commons-imaging:1.0-alpha2",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/apache/commons/commons-imaging/1.0-alpha2/commons-imaging-1.0-alpha2.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/apache/commons/commons-imaging/1.0-alpha2/commons-imaging-1.0-alpha2.jar"
+ ],
+ "sha256": "64d649007364d70dcab24a1f895646e6976f5e2b339ba73a4af20642d041666a",
+ "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-imaging/1.0-alpha2/commons-imaging-1.0-alpha2.jar"
+ },
+ {
+ "coord": "org.apache.logging.log4j:log4j-api:2.14.1",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar"
+ ],
+ "sha256": "8caf58db006c609949a0068110395a33067a2bad707c3da35e959c0473f9a916",
+ "url": "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api/2.14.1/log4j-api-2.14.1.jar"
+ },
+ {
+ "coord": "org.apache.logging.log4j:log4j-core:2.14.1",
+ "dependencies": [
+ "org.apache.logging.log4j:log4j-api:2.14.1"
+ ],
+ "directDependencies": [
+ "org.apache.logging.log4j:log4j-api:2.14.1"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar"
+ ],
+ "sha256": "ade7402a70667a727635d5c4c29495f4ff96f061f12539763f6f123973b465b0",
+ "url": "https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core/2.14.1/log4j-core-2.14.1.jar"
+ },
+ {
+ "coord": "org.hamcrest:hamcrest-core:1.3",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
+ ],
+ "sha256": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9",
+ "url": "https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"
+ },
+ {
+ "coord": "org.hibernate:hibernate-validator:5.2.4.Final",
+ "dependencies": [
+ "org.jboss.logging:jboss-logging:3.2.1.Final",
+ "com.fasterxml:classmate:1.1.0",
+ "javax.validation:validation-api:2.0.1.Final"
+ ],
+ "directDependencies": [
+ "com.fasterxml:classmate:1.1.0",
+ "javax.validation:validation-api:2.0.1.Final",
+ "org.jboss.logging:jboss-logging:3.2.1.Final"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar"
+ ],
+ "sha256": "fc7e2ed4079859f61390932a4f4cd5b2447e1ebc77d4915badb1a0655588697a",
+ "url": "https://repo1.maven.org/maven2/org/hibernate/hibernate-validator/5.2.4.Final/hibernate-validator-5.2.4.Final.jar"
+ },
+ {
+ "coord": "org.jboss.logging:jboss-logging:3.2.1.Final",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar"
+ ],
+ "sha256": "a3b0ffa8ae2b2f2387ebdfdce29086d3955d2a46ce7da802c2ba6ae47fa2f1bf",
+ "url": "https://repo1.maven.org/maven2/org/jboss/logging/jboss-logging/3.2.1.Final/jboss-logging-3.2.1.Final.jar"
+ },
+ {
+ "coord": "org.jetbrains.kotlin:kotlin-reflect:1.4.31",
+ "dependencies": [
+ "org.jetbrains.kotlin:kotlin-stdlib:1.4.31",
+ "org.jetbrains:annotations:13.0",
+ "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.31"
+ ],
+ "directDependencies": [
+ "org.jetbrains.kotlin:kotlin-stdlib:1.4.31"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.4.31/kotlin-reflect-1.4.31.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.4.31/kotlin-reflect-1.4.31.jar"
+ ],
+ "sha256": "91fad0b42974a7d5811e30a61f05706e176b144235717c6de7e81e3a781028f2",
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.4.31/kotlin-reflect-1.4.31.jar"
+ },
+ {
+ "coord": "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.31",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.4.31/kotlin-stdlib-common-1.4.31.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.4.31/kotlin-stdlib-common-1.4.31.jar"
+ ],
+ "sha256": "57962f44371a746b678218a0802a8712c6255206de9a69ede215e3aa4b044708",
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.4.31/kotlin-stdlib-common-1.4.31.jar"
+ },
+ {
+ "coord": "org.jetbrains.kotlin:kotlin-stdlib:1.4.31",
+ "dependencies": [
+ "org.jetbrains:annotations:13.0",
+ "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.31"
+ ],
+ "directDependencies": [
+ "org.jetbrains:annotations:13.0",
+ "org.jetbrains.kotlin:kotlin-stdlib-common:1.4.31"
+ ],
+ "file": "v1/https/repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.4.31/kotlin-stdlib-1.4.31.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.4.31/kotlin-stdlib-1.4.31.jar"
+ ],
+ "sha256": "76a599d88b167e8ac90879b6daa722c6ad3452ba714c9aba19bd196544b97f1c",
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.4.31/kotlin-stdlib-1.4.31.jar"
+ },
+ {
+ "coord": "org.jetbrains:annotations:13.0",
+ "dependencies": [],
+ "directDependencies": [],
+ "file": "v1/https/repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar",
+ "mirror_urls": [
+ "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar"
+ ],
+ "sha256": "ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478",
+ "url": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar"
+ }
+ ],
+ "version": "0.1.0"
+ }
+}
diff --git a/repositories.bzl b/repositories.bzl
new file mode 100644
index 00000000..36b34443
--- /dev/null
+++ b/repositories.bzl
@@ -0,0 +1,137 @@
+# Copyright 2021 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.
+
+"""Contains the external dependencies required to build Jazzer (but not the examples)."""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+def jazzer_dependencies():
+ maybe(
+ http_archive,
+ name = "platforms",
+ sha256 = "079945598e4b6cc075846f7fd6a9d0857c33a7afc0de868c2ccb96405225135d",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.4/platforms-0.0.4.tar.gz",
+ "https://github.com/bazelbuild/platforms/releases/download/0.0.4/platforms-0.0.4.tar.gz",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "bazel_skylib",
+ sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+ urls = [
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "io_bazel_rules_kotlin",
+ sha256 = "6cbd4e5768bdfae1598662e40272729ec9ece8b7bded8f0d2c81c8ff96dc139d",
+ url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v1.5.0-beta-4/rules_kotlin_release.tgz",
+ )
+
+ maybe(
+ http_archive,
+ name = "com_google_glog",
+ repo_mapping = {"@com_github_gflags_gflags": "@jazzer_com_github_gflags_gflags"},
+ sha256 = "5a39d51a6058348e6b683f5343a24d94e01c518c7a045101045e301a27efab13",
+ strip_prefix = "glog-a4a725d547a6c1329607db50af044c4fa329e07a",
+ url = "https://github.com/google/glog/archive/a4a725d547a6c1329607db50af044c4fa329e07a.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "com_google_absl",
+ sha256 = "5e1cbf25bf501f8e37866000a6052d02dbdd7b19a5b592251c59a4c9aa5c71ae",
+ strip_prefix = "abseil-cpp-f2dbd918d8d08529800eb72f23bd2829f92104a4",
+ url = "https://github.com/abseil/abseil-cpp/archive/f2dbd918d8d08529800eb72f23bd2829f92104a4.zip",
+ )
+
+ maybe(
+ http_archive,
+ name = "com_github_johnynek_bazel_jar_jar",
+ sha256 = "97c5f862482a05f385bd8f9d28a9bbf684b0cf3fae93112ee96f3fb04d34b193",
+ strip_prefix = "bazel_jar_jar-171f268569384c57c19474b04aebe574d85fde0d",
+ url = "https://github.com/johnynek/bazel_jar_jar/archive/171f268569384c57c19474b04aebe574d85fde0d.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "com_github_jhalterman_typetools",
+ build_file = Label("//third_party:typetools.BUILD"),
+ sha256 = "754f46de7d4c278cee2d4dba3c09ebe08fde03d0e67fc85d700611d9cdfb7868",
+ strip_prefix = "typetools-887153d2a9adf032fac9f145594d0a0248618d48",
+ url = "https://github.com/jhalterman/typetools/archive/887153d2a9adf032fac9f145594d0a0248618d48.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ build_file = Label("//third_party:classgraph.BUILD"),
+ name = "com_github_classgraph_classgraph",
+ sha256 = "535159d80c163d5b4d025c402b4562c92ed2d6d963db8c6c5255c0eb2c4e9f39",
+ strip_prefix = "classgraph-classgraph-4.8.128",
+ url = "https://github.com/classgraph/classgraph/archive/refs/tags/classgraph-4.8.128.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "fmeum_rules_jni",
+ sha256 = "8d685e381cb625e11fac330085de2ebc13ad497d30c4e9b09beb212f7c27e8e7",
+ url = "https://github.com/fmeum/rules_jni/releases/download/v0.3.0/rules_jni-v0.3.0.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ build_file = Label("//third_party:asm.BUILD"),
+ name = "jazzer_ow2_asm",
+ sha256 = "7b596cc584b241619911e99c5c96366fccd533b1a50b8720c151c2f74b5915e3",
+ strip_prefix = "asm-ASM_9_2",
+ url = "https://gitlab.ow2.org/asm/asm/-/archive/ASM_9_2/asm-ASM_9_2.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "jazzer_com_github_gflags_gflags",
+ patches = [
+ Label("//third_party:gflags-use-double-dash-args.patch"),
+ ],
+ sha256 = "ce2931dd537eaab7dab78b25bec6136a0756ca0b2acbdab9aec0266998c0d9a7",
+ strip_prefix = "gflags-827c769e5fc98e0f2a34c47cef953cc6328abced",
+ url = "https://github.com/gflags/gflags/archive/827c769e5fc98e0f2a34c47cef953cc6328abced.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "jazzer_jacoco",
+ build_file = Label("//third_party:jacoco_internal.BUILD"),
+ patches = [
+ Label("//third_party:jacoco-make-probe-adapter-subclassable.patch"),
+ Label("//third_party:jacoco-make-probe-inserter-subclassable.patch"),
+ ],
+ sha256 = "4a3c65b8a8ca58ffcec77288820f557ed93125e8a0b43dd7460b776c58bb8ed9",
+ strip_prefix = "jacoco-0.8.7-jazzer",
+ url = "https://github.com/CodeIntelligenceTesting/jacoco/archive/v0.8.7-jazzer.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "jazzer_libfuzzer",
+ build_file = Label("//third_party:libFuzzer.BUILD"),
+ sha256 = "efde37ab5a9e4fff67f8cd43b701be5ea5ddb74a3bc10e4d8e91a614070145c3",
+ url = "https://github.com/CodeIntelligenceTesting/llvm-project-jazzer/releases/download/2021-11-30/jazzer-libfuzzer-2021-11-30.tar.gz",
+ )
diff --git a/sanitizers/BUILD.bazel b/sanitizers/BUILD.bazel
new file mode 100644
index 00000000..fa84208e
--- /dev/null
+++ b/sanitizers/BUILD.bazel
@@ -0,0 +1,8 @@
+java_library(
+ name = "sanitizers",
+ visibility = ["//visibility:public"],
+ runtime_deps = [
+ "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers",
+ "//sanitizers/src/main/java/jaz",
+ ],
+)
diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl
new file mode 100644
index 00000000..8bdea7a9
--- /dev/null
+++ b/sanitizers/sanitizers.bzl
@@ -0,0 +1,24 @@
+# Copyright 2021 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.
+
+_sanitizer_package_prefix = "com.code_intelligence.jazzer.sanitizers."
+
+_sanitizer_class_names = [
+ "Deserialization",
+ "ExpressionLanguageInjection",
+ "NamingContextLookup",
+ "ReflectiveCall",
+]
+
+SANITIZER_CLASSES = [_sanitizer_package_prefix + class_name for class_name in _sanitizer_class_names]
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
new file mode 100644
index 00000000..65480653
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel
@@ -0,0 +1,17 @@
+load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
+
+kt_jvm_library(
+ name = "sanitizers",
+ srcs = [
+ "Deserialization.kt",
+ "ExpressionLanguageInjection.kt",
+ "NamingContextLookup.kt",
+ "ReflectiveCall.kt",
+ "Utils.kt",
+ ],
+ visibility = ["//sanitizers:__pkg__"],
+ deps = [
+ "//agent:jazzer_api_compile_only",
+ "//sanitizers/src/main/java/jaz",
+ ],
+)
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt
new file mode 100644
index 00000000..f6401dfd
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Deserialization.kt
@@ -0,0 +1,168 @@
+// Copyright 2021 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.sanitizers
+
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.MethodHook
+import com.code_intelligence.jazzer.api.MethodHooks
+import java.io.BufferedInputStream
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.ObjectInputStream
+import java.io.ObjectOutputStream
+import java.io.ObjectStreamConstants
+import java.lang.invoke.MethodHandle
+import java.util.WeakHashMap
+
+/**
+ * Detects unsafe deserialization that leads to attacker-controlled method calls, in particular to [Object.finalize].
+ */
+@Suppress("unused_parameter")
+object Deserialization {
+
+ private val OBJECT_INPUT_STREAM_HEADER =
+ ObjectStreamConstants.STREAM_MAGIC.toBytes() + ObjectStreamConstants.STREAM_VERSION.toBytes()
+
+ /**
+ * Used to memoize the [InputStream] used to construct a given [ObjectInputStream].
+ * [ThreadLocal] is required because the map is not synchronized (and likely cheaper than
+ * synchronization).
+ * [WeakHashMap] ensures that we don't prevent the GC from cleaning up [ObjectInputStream] from
+ * previous fuzzing runs.
+ *
+ * Note: The [InputStream] values can all be assumed to be markable, i.e., their
+ * [InputStream.markSupported] returns true.
+ */
+ private var inputStreamForObjectInputStream: ThreadLocal<WeakHashMap<ObjectInputStream, InputStream>> =
+ ThreadLocal.withInitial {
+ WeakHashMap<ObjectInputStream, InputStream>()
+ }
+
+ /**
+ * A serialized instance of our honeypot class.
+ */
+ private val SERIALIZED_JAZ_ZER_INSTANCE: ByteArray by lazy {
+ // We can't instantiate jaz.Zer directly, so we instantiate and serialize jaz.Ter and then
+ // patch the class name.
+ val baos = ByteArrayOutputStream()
+ ObjectOutputStream(baos).writeObject(jaz.Ter())
+ val serializedJazTerInstance = baos.toByteArray()
+ val posToPatch = serializedJazTerInstance.indexOf("jaz.Ter".toByteArray())
+ serializedJazTerInstance[posToPatch + "jaz.".length] = 'Z'.code.toByte()
+ serializedJazTerInstance
+ }
+
+ /**
+ * Guides the fuzzer towards producing a valid header for an ObjectInputStream.
+ */
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.io.ObjectInputStream",
+ targetMethod = "<init>",
+ targetMethodDescriptor = "(Ljava/io/InputStream;)V"
+ )
+ @JvmStatic
+ fun objectInputStreamInitBeforeHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ val originalInputStream = args[0] as? InputStream ?: return
+ val fixedInputStream = if (originalInputStream.markSupported())
+ originalInputStream
+ else
+ BufferedInputStream(originalInputStream)
+ args[0] = fixedInputStream
+ guideMarkableInputStreamTowardsEquality(fixedInputStream, OBJECT_INPUT_STREAM_HEADER, hookId)
+ }
+
+ /**
+ * Memoizes the input stream used for creating the [ObjectInputStream] instance.
+ */
+ @MethodHook(
+ type = HookType.AFTER,
+ targetClassName = "java.io.ObjectInputStream",
+ targetMethod = "<init>",
+ targetMethodDescriptor = "(Ljava/io/InputStream;)V"
+ )
+ @JvmStatic
+ fun objectInputStreamInitAfterHook(
+ method: MethodHandle?,
+ objectInputStream: ObjectInputStream?,
+ args: Array<Any?>,
+ hookId: Int,
+ alwaysNull: Any?,
+ ) {
+ val inputStream = args[0] as? InputStream
+ check(inputStream?.markSupported() == true) {
+ "ObjectInputStream#<init> AFTER hook reached with null or non-markable input stream"
+ }
+ inputStreamForObjectInputStream.get()[objectInputStream] = inputStream
+ }
+
+ /**
+ * Guides the fuzzer towards producing a valid serialized instance of our honeypot class.
+ */
+ @MethodHooks(
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.io.ObjectInputStream",
+ targetMethod = "readObject"
+ ),
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.io.ObjectInputStream",
+ targetMethod = "readObjectOverride"
+ ),
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "java.io.ObjectInputStream",
+ targetMethod = "readUnshared"
+ ),
+ )
+ @JvmStatic
+ fun readObjectBeforeHook(
+ method: MethodHandle?,
+ objectInputStream: ObjectInputStream?,
+ args: Array<Any?>,
+ hookId: Int,
+ ) {
+ val inputStream = inputStreamForObjectInputStream.get()[objectInputStream]
+ if (inputStream?.markSupported() != true) return
+ guideMarkableInputStreamTowardsEquality(inputStream, SERIALIZED_JAZ_ZER_INSTANCE, hookId)
+ }
+
+ /**
+ * Calls [Object.finalize] early if the returned object is [jaz.Zer]. A call to finalize is
+ * guaranteed to happen at some point, but calling it early means that we can accurately report
+ * the input that lead to its execution.
+ */
+ @MethodHooks(
+ MethodHook(type = HookType.AFTER, targetClassName = "java.io.ObjectInputStream", targetMethod = "readObject"),
+ MethodHook(type = HookType.AFTER, targetClassName = "java.io.ObjectInputStream", targetMethod = "readObjectOverride"),
+ MethodHook(type = HookType.AFTER, targetClassName = "java.io.ObjectInputStream", targetMethod = "readUnshared"),
+ )
+ @JvmStatic
+ fun readObjectAfterHook(
+ method: MethodHandle?,
+ objectInputStream: ObjectInputStream?,
+ args: Array<Any?>,
+ hookId: Int,
+ deserializedObject: Any?,
+ ) {
+ if (deserializedObject?.javaClass?.name == HONEYPOT_CLASS_NAME) {
+ deserializedObject.javaClass.getDeclaredMethod("finalize").run {
+ isAccessible = true
+ invoke(deserializedObject)
+ }
+ }
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt
new file mode 100644
index 00000000..9b1e8ca6
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ExpressionLanguageInjection.kt
@@ -0,0 +1,83 @@
+
+// Copyright 2021 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.sanitizers
+
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.Jazzer
+import com.code_intelligence.jazzer.api.MethodHook
+import com.code_intelligence.jazzer.api.MethodHooks
+import java.lang.invoke.MethodHandle
+
+/**
+ * Detects injectable inputs to an expression language interpreter which may lead to remote code execution.
+ */
+@Suppress("unused_parameter")
+object ExpressionLanguageInjection {
+
+ /**
+ * Try to call the default constructor of the honeypot class.
+ */
+ private const val EXPRESSION_LANGUAGE_ATTACK =
+ "\${\"\".getClass().forName(\"$HONEYPOT_CLASS_NAME\").newInstance()}"
+
+ @MethodHooks(
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "javax.el.ExpressionFactory",
+ targetMethod = "createValueExpression",
+ ),
+ MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "javax.el.ExpressionFactory",
+ targetMethod = "createMethodExpression",
+ ),
+ )
+ @JvmStatic
+ fun hookElExpressionFactory(
+ method: MethodHandle?,
+ thisObject: Any?,
+ arguments: Array<Any>,
+ hookId: Int
+ ) {
+ if (arguments[1] is String) {
+ val expression = arguments[1] as String
+ Jazzer.guideTowardsContainment(expression, EXPRESSION_LANGUAGE_ATTACK, hookId)
+ }
+ }
+
+ // With default configurations the argument to
+ // ConstraintValidatorContext.buildConstraintViolationWithTemplate() will be evaluated by an
+ // Expression Language interpreter which allows arbitrary code execution if the attacker has
+ // control of the method argument.
+ //
+ // References: CVE-2018-16621
+ // https://securitylab.github.com/research/bean-validation-RCE/
+ @MethodHook(
+ type = HookType.BEFORE,
+ targetClassName = "javax.validation.ConstraintValidatorContext",
+ targetMethod = "buildConstraintViolationWithTemplate"
+ )
+ @JvmStatic
+ fun hookBuildConstraintViolationWithTemplate(
+ method: MethodHandle?,
+ thisObject: Any?,
+ arguments: Array<Any>,
+ hookId: Int
+ ) {
+ val message = arguments[0] as String
+ Jazzer.guideTowardsContainment(message, EXPRESSION_LANGUAGE_ATTACK, hookId)
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt
new file mode 100644
index 00000000..2d4fb9cf
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/NamingContextLookup.kt
@@ -0,0 +1,102 @@
+// Copyright 2021 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.sanitizers
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.Jazzer
+import com.code_intelligence.jazzer.api.MethodHook
+import com.code_intelligence.jazzer.api.MethodHooks
+import java.lang.invoke.MethodHandle
+import javax.naming.CommunicationException
+
+object NamingContextLookup {
+
+ // The particular URL g.co is used here since it is:
+ // - short, which makes it easier for the fuzzer to incorporate into the input;
+ // - valid, which means that a `lookup` call on it could actually result in RCE;
+ // - highly reputable, which makes it very unlikely that it would ever host an actual exploit.
+ private const val LDAP_MARKER = "ldap://g.co/"
+ private const val RMI_MARKER = "rmi://g.co/"
+
+ @MethodHooks(
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.Context",
+ targetMethod = "lookup",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialContext",
+ targetMethod = "lookup",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialDirContext",
+ targetMethod = "lookup",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialLdapContext",
+ targetMethod = "lookup",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.Context",
+ targetMethod = "lookupLink",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialContext",
+ targetMethod = "lookupLink",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialDirContext",
+ targetMethod = "lookupLink",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ MethodHook(
+ type = HookType.REPLACE,
+ targetClassName = "javax.naming.InitialLdapContext",
+ targetMethod = "lookupLink",
+ targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Object;",
+ ),
+ )
+ @JvmStatic
+ fun lookupHook(method: MethodHandle?, thisObject: Any?, args: Array<Any?>, hookId: Int): Any {
+ val name = args[0] as String
+ if (name.startsWith(RMI_MARKER) || name.startsWith(LDAP_MARKER)) {
+ Jazzer.reportFindingFromHook(
+ FuzzerSecurityIssueCritical(
+ """Remote JNDI Lookup
+JNDI lookups with attacker-controlled remote URLs can, depending on the JDK
+version, lead to remote code execution or the exfiltration of information."""
+ )
+ )
+ }
+ Jazzer.guideTowardsEquality(name, RMI_MARKER, hookId)
+ Jazzer.guideTowardsEquality(name, LDAP_MARKER, 31 * hookId)
+ // Pretend that the remote endpoint could not be reached for additional protection against
+ // accidental execution of remote code during fuzzing.
+ throw CommunicationException()
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt
new file mode 100644
index 00000000..7842d879
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/ReflectiveCall.kt
@@ -0,0 +1,34 @@
+// Copyright 2021 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.sanitizers
+
+import com.code_intelligence.jazzer.api.HookType
+import com.code_intelligence.jazzer.api.Jazzer
+import com.code_intelligence.jazzer.api.MethodHook
+import java.lang.invoke.MethodHandle
+
+/**
+ * Detects unsafe reflective calls that lead to attacker-controlled method calls.
+ */
+@Suppress("unused_parameter")
+object ReflectiveCall {
+
+ @MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Class", targetMethod = "forName")
+ @JvmStatic
+ fun classForNameHook(method: MethodHandle?, alwaysNull: Any?, args: Array<Any?>, hookId: Int) {
+ val className = args[0] as? String ?: return
+ Jazzer.guideTowardsEquality(className, HONEYPOT_CLASS_NAME, hookId)
+ }
+}
diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt
new file mode 100644
index 00000000..3166773b
--- /dev/null
+++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/Utils.kt
@@ -0,0 +1,51 @@
+// Copyright 2021 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.sanitizers
+
+import com.code_intelligence.jazzer.api.Jazzer
+import java.io.InputStream
+
+/**
+ * jaz.Zer is a honeypot class: All of its methods report a finding when called.
+ */
+const val HONEYPOT_CLASS_NAME = "jaz.Zer"
+
+internal fun Short.toBytes(): ByteArray {
+ return byteArrayOf(
+ ((toInt() shr 8) and 0xFF).toByte(),
+ (toInt() and 0xFF).toByte(),
+ )
+}
+
+// Runtime is only O(size * needle.size), only use for small arrays.
+internal fun ByteArray.indexOf(needle: ByteArray): Int {
+ outer@ for (i in 0 until size - needle.size + 1) {
+ for (j in needle.indices) {
+ if (this[i + j] != needle[j]) {
+ continue@outer
+ }
+ }
+ return i
+ }
+ return -1
+}
+
+internal fun guideMarkableInputStreamTowardsEquality(stream: InputStream, target: ByteArray, id: Int) {
+ check(stream.markSupported())
+ stream.mark(target.size)
+ val current = stream.readNBytes(target.size)
+ stream.reset()
+ Jazzer.guideTowardsEquality(current, target, id)
+}
diff --git a/sanitizers/src/main/java/jaz/BUILD.bazel b/sanitizers/src/main/java/jaz/BUILD.bazel
new file mode 100644
index 00000000..81275a31
--- /dev/null
+++ b/sanitizers/src/main/java/jaz/BUILD.bazel
@@ -0,0 +1,12 @@
+java_library(
+ name = "jaz",
+ srcs = [
+ "Ter.java",
+ "Zer.java",
+ ],
+ visibility = [
+ "//sanitizers:__pkg__",
+ "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:__pkg__",
+ ],
+ deps = ["//agent:jazzer_api_compile_only"],
+)
diff --git a/sanitizers/src/main/java/jaz/Ter.java b/sanitizers/src/main/java/jaz/Ter.java
new file mode 100644
index 00000000..7814396f
--- /dev/null
+++ b/sanitizers/src/main/java/jaz/Ter.java
@@ -0,0 +1,24 @@
+// Copyright 2021 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 jaz;
+
+/**
+ * A safe to use companion of {@link jaz.Zer} that is used to produce serializable instances of it
+ * with only light patching.
+ */
+@SuppressWarnings("unused")
+public class Ter implements java.io.Serializable {
+ static final long serialVersionUID = 42L;
+}
diff --git a/sanitizers/src/main/java/jaz/Zer.java b/sanitizers/src/main/java/jaz/Zer.java
new file mode 100644
index 00000000..0b27609c
--- /dev/null
+++ b/sanitizers/src/main/java/jaz/Zer.java
@@ -0,0 +1,107 @@
+// Copyright 2021 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 jaz;
+
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
+import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
+import com.code_intelligence.jazzer.api.Jazzer;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * A honeypot class that reports an appropriate finding on any interaction with one of its methods
+ * or initializers.
+ *
+ * Note: This class must not be referenced in any way by the rest of the code, not even statically.
+ * When referring to it, always use its hardcoded class name "jaz.Zer".
+ */
+@SuppressWarnings("unused")
+public class Zer implements java.io.Serializable {
+ static final long serialVersionUID = 42L;
+
+ private static final Throwable staticInitializerCause;
+
+ static {
+ staticInitializerCause = new FuzzerSecurityIssueMedium("finalize call on arbitrary object");
+ }
+
+ public Zer() {
+ Jazzer.reportFindingFromHook(
+ new FuzzerSecurityIssueMedium("default constructor call on arbitrary object"));
+ }
+
+ public Zer(String arg1) {
+ Jazzer.reportFindingFromHook(
+ new FuzzerSecurityIssueMedium("String constructor call on arbitrary object"));
+ }
+
+ public Zer(String arg1, Throwable arg2) {
+ Jazzer.reportFindingFromHook(
+ new FuzzerSecurityIssueMedium("(String, Throwable) constructor call on arbitrary object"));
+ }
+
+ private String jaz;
+
+ public String getJaz() {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("getter call on arbitrary object"));
+ return jaz;
+ }
+
+ public void setJaz(String jaz) {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("setter call on arbitrary object"));
+ this.jaz = jaz;
+ }
+
+ @Override
+ public int hashCode() {
+ Jazzer.reportFindingFromHook(
+ new FuzzerSecurityIssueMedium("hashCode call on arbitrary object"));
+ return super.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("equals call on arbitrary object"));
+ return super.equals(obj);
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueMedium("clone call on arbitrary object"));
+ return super.clone();
+ }
+
+ @Override
+ public String toString() {
+ Jazzer.reportFindingFromHook(
+ new FuzzerSecurityIssueMedium("toString call on arbitrary object"));
+ return super.toString();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ // finalize is invoked automatically by the GC with an uninformative stack trace. We use the
+ // stack trace prerecorded in the static initializer.
+ Jazzer.reportFindingFromHook(staticInitializerCause);
+ super.finalize();
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ Jazzer.reportFindingFromHook(new FuzzerSecurityIssueHigh("Remote Code Execution\n"
+ + " Deserialization of arbitrary classes with custom readObject may allow remote\n"
+ + " code execution depending on the classpath."));
+ in.defaultReadObject();
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel
new file mode 100644
index 00000000..d148545a
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/BUILD.bazel
@@ -0,0 +1,32 @@
+load("//bazel:fuzz_target.bzl", "java_fuzz_target_test")
+
+java_fuzz_target_test(
+ name = "ObjectInputStreamDeserialization",
+ srcs = [
+ "ObjectInputStreamDeserialization.java",
+ ],
+ target_class = "com.example.ObjectInputStreamDeserialization",
+)
+
+java_fuzz_target_test(
+ name = "ReflectiveCall",
+ srcs = [
+ "ReflectiveCall.java",
+ ],
+ target_class = "com.example.ReflectiveCall",
+)
+
+java_fuzz_target_test(
+ name = "ExpressionLanguageInjection",
+ srcs = [
+ "ExpressionLanguageInjection.java",
+ "InsecureEmailValidator.java",
+ ],
+ target_class = "com.example.ExpressionLanguageInjection",
+ deps = [
+ "@maven//:javax_el_javax_el_api",
+ "@maven//:javax_validation_validation_api",
+ "@maven//:javax_xml_bind_jaxb_api",
+ "@maven//:org_hibernate_hibernate_validator",
+ ],
+)
diff --git a/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java
new file mode 100644
index 00000000..e26a9117
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/ExpressionLanguageInjection.java
@@ -0,0 +1,49 @@
+// Copyright 2021 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.validation.*;
+
+class UserData {
+ public UserData(String email) {
+ this.email = email;
+ }
+
+ @ValidEmailConstraint private String email;
+}
+
+@Constraint(validatedBy = InsecureEmailValidator.class)
+@Target({ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@interface ValidEmailConstraint {
+ String message() default "Invalid email address";
+ Class<?>[] groups() default {};
+ Class<? extends Payload>[] payload() default {};
+}
+
+public class ExpressionLanguageInjection {
+ final private static Validator validator =
+ Validation.buildDefaultValidatorFactory().getValidator();
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ UserData uncheckedUserData = new UserData(data.consumeRemainingAsString());
+ validator.validate(uncheckedUserData);
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/InsecureEmailValidator.java b/sanitizers/src/test/java/com/example/InsecureEmailValidator.java
new file mode 100644
index 00000000..d61e888d
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/InsecureEmailValidator.java
@@ -0,0 +1,36 @@
+// Copyright 2021 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 static java.lang.String.format;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class InsecureEmailValidator implements ConstraintValidator<ValidEmailConstraint, String> {
+ @Override
+ public void initialize(ValidEmailConstraint email) {}
+
+ @Override
+ public boolean isValid(String email, ConstraintValidatorContext cxt) {
+ if (email == null || !email.matches(".+@.+\\..+")) {
+ // Insecure: do not call buildConstraintViolationWithTemplate with untrusted data!
+ cxt.buildConstraintViolationWithTemplate(format("Invalid email address: %s", email))
+ .addConstraintViolation();
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/ObjectInputStreamDeserialization.java b/sanitizers/src/test/java/com/example/ObjectInputStreamDeserialization.java
new file mode 100644
index 00000000..c6285609
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/ObjectInputStreamDeserialization.java
@@ -0,0 +1,32 @@
+// Copyright 2021 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 java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+public class ObjectInputStreamDeserialization {
+ public static void fuzzerTestOneInput(byte[] data) {
+ try {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
+ ois.readObject();
+ } catch (IOException | ClassNotFoundException ignored) {
+ // Ignored checked exception.
+ } catch (NullPointerException | NegativeArraySizeException ignored) {
+ // Ignored RuntimeExceptions thrown by readObject().
+ }
+ }
+}
diff --git a/sanitizers/src/test/java/com/example/ReflectiveCall.java b/sanitizers/src/test/java/com/example/ReflectiveCall.java
new file mode 100644
index 00000000..7f85e486
--- /dev/null
+++ b/sanitizers/src/test/java/com/example/ReflectiveCall.java
@@ -0,0 +1,32 @@
+// Copyright 2021 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 java.lang.reflect.InvocationTargetException;
+
+public class ReflectiveCall {
+ public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+ String input = data.consumeRemainingAsAsciiString();
+ if (input.startsWith("@")) {
+ String className = input.substring(1);
+ try {
+ Class.forName(className).getConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException
+ | NoSuchMethodException | ClassNotFoundException ignored) {
+ }
+ }
+ }
+}
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
new file mode 100644
index 00000000..0643e4f6
--- /dev/null
+++ b/third_party/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+
+bool_flag(
+ name = "toolchain",
+ build_setting_default = False,
+)
+
+config_setting(
+ name = "uses_toolchain",
+ flag_values = {
+ ":toolchain": "true",
+ },
+ visibility = ["//visibility:public"],
+)
diff --git a/third_party/asm.BUILD b/third_party/asm.BUILD
new file mode 100644
index 00000000..2f659fc6
--- /dev/null
+++ b/third_party/asm.BUILD
@@ -0,0 +1,32 @@
+java_library(
+ name = "asm",
+ srcs = glob(["asm/src/main/**/*.java"]),
+ visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "asm_commons",
+ srcs = glob(["asm-commons/src/main/**/*.java"]),
+ deps = [
+ ":asm",
+ ":asm_analysis",
+ ":asm_tree",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "asm_tree",
+ srcs = glob(["asm-tree/src/main/**/*.java"]),
+ deps = [":asm"],
+ visibility = ["//visibility:public"],
+)
+
+java_library(
+ name = "asm_analysis",
+ srcs = glob(["asm-analysis/src/main/**/*.java"]),
+ deps = [
+ ":asm",
+ ":asm_tree",
+ ],
+)
diff --git a/third_party/bazel-toolchain-export-dynamic-macos-asan.patch b/third_party/bazel-toolchain-export-dynamic-macos-asan.patch
new file mode 100644
index 00000000..05020e28
--- /dev/null
+++ b/third_party/bazel-toolchain-export-dynamic-macos-asan.patch
@@ -0,0 +1,12 @@
+diff --git toolchain/BUILD.llvm_repo toolchain/BUILD.llvm_repo
+--- toolchain/BUILD.llvm_repo
++++ toolchain/BUILD.llvm_repo
+@@ -124,3 +124,8 @@ filegroup(
+ name = "strip",
+ srcs = ["bin/llvm-strip"],
+ )
++
++cc_import(
++ name = "macos_asan_dynamic",
++ shared_library = "lib/clang/13.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib",
++)
diff --git a/third_party/classgraph.BUILD b/third_party/classgraph.BUILD
new file mode 100644
index 00000000..ab51f11c
--- /dev/null
+++ b/third_party/classgraph.BUILD
@@ -0,0 +1,8 @@
+java_library(
+ name = "classgraph",
+ srcs = glob([
+ "src/main/java/io/github/classgraph/**/*.java",
+ "src/main/java/nonapi/io/github/classgraph/**/*.java",
+ ]),
+ visibility = ["//visibility:public"],
+)
diff --git a/third_party/gflags-use-double-dash-args.patch b/third_party/gflags-use-double-dash-args.patch
new file mode 100644
index 00000000..554b41bd
--- /dev/null
+++ b/third_party/gflags-use-double-dash-args.patch
@@ -0,0 +1,11 @@
+--- src/gflags_reporting.cc
++++ src/gflags_reporting.cc
+@@ -118,7 +118,7 @@
+ // Goes to some trouble to make pretty line breaks.
+ string DescribeOneFlag(const CommandLineFlagInfo& flag) {
+ string main_part;
+- SStringPrintf(&main_part, " -%s (%s)",
++ SStringPrintf(&main_part, " --%s (%s)",
+ flag.name.c_str(),
+ flag.description.c_str());
+ const char* c_string = main_part.c_str();
diff --git a/third_party/jacoco-make-probe-adapter-subclassable.patch b/third_party/jacoco-make-probe-adapter-subclassable.patch
new file mode 100644
index 00000000..7e51195a
--- /dev/null
+++ b/third_party/jacoco-make-probe-adapter-subclassable.patch
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: EPL-2.0 and Apache-2.0
+// These patches apply to JaCoCo (https://github.com/jacoco/jacoco) and are hereby made available under the terms of the
+// Eclipse Public License 2.0 available at:
+// http://www.eclipse.org/legal/epl-2.0
+diff --git org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
+index 0cc06ada..b65efb03 100644
+--- org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
++++ org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
+@@ -31,6 +31,8 @@ import org.jacoco.core.internal.analysis.ClassCoverageImpl;
+ import org.jacoco.core.internal.analysis.StringPool;
+ import org.jacoco.core.internal.data.CRC64;
+ import org.jacoco.core.internal.flow.ClassProbesAdapter;
++import org.jacoco.core.internal.flow.ClassProbesVisitor;
++import org.jacoco.core.internal.flow.IClassProbesAdapterFactory;
+ import org.jacoco.core.internal.instr.InstrSupport;
+ import org.objectweb.asm.ClassReader;
+ import org.objectweb.asm.ClassVisitor;
+@@ -52,6 +54,8 @@ public class Analyzer {
+
+ private final StringPool stringPool;
+
++ private final IClassProbesAdapterFactory classProbesAdapterFactory;
++
+ /**
+ * Creates a new analyzer reporting to the given output.
+ *
+@@ -63,9 +67,21 @@ public class Analyzer {
+ */
+ public Analyzer(final ExecutionDataStore executionData,
+ final ICoverageVisitor coverageVisitor) {
++ this(executionData, coverageVisitor, new IClassProbesAdapterFactory() {
++ @Override
++ public ClassProbesAdapter makeClassProbesAdapter(ClassProbesVisitor cv, boolean trackFrames) {
++ return new ClassProbesAdapter(cv, trackFrames);
++ };
++ });
++ }
++
++ public Analyzer(final ExecutionDataStore executionData,
++ final ICoverageVisitor coverageVisitor,
++ final IClassProbesAdapterFactory classProbesAdapterFactory) {
+ this.executionData = executionData;
+ this.coverageVisitor = coverageVisitor;
+ this.stringPool = new StringPool();
++ this.classProbesAdapterFactory = classProbesAdapterFactory;
+ }
+
+ /**
+@@ -99,7 +115,7 @@ public class Analyzer {
+ coverageVisitor.visitCoverage(coverage);
+ }
+ };
+- return new ClassProbesAdapter(analyzer, false);
++ return classProbesAdapterFactory.makeClassProbesAdapter(analyzer, false);
+ }
+
+ private void analyzeClass(final byte[] source) {
+diff --git org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java
+new file mode 100644
+index 00000000..45fc2709
+--- /dev/null
++++ org.jacoco.core/src/org/jacoco/core/internal/flow/IClassProbesAdapterFactory.java
+@@ -0,0 +1,6 @@
++package org.jacoco.core.internal.flow;
++
++public interface IClassProbesAdapterFactory {
++ ClassProbesAdapter makeClassProbesAdapter(ClassProbesVisitor cv,
++ boolean trackFrames);
++}
diff --git a/third_party/jacoco-make-probe-inserter-subclassable.patch b/third_party/jacoco-make-probe-inserter-subclassable.patch
new file mode 100644
index 00000000..3885fa1f
--- /dev/null
+++ b/third_party/jacoco-make-probe-inserter-subclassable.patch
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: EPL-2.0 and Apache-2.0
+// These patches apply to JaCoCo (https://github.com/jacoco/jacoco) and are hereby made available under the terms of the
+// Eclipse Public License 2.0 available at:
+// http://www.eclipse.org/legal/epl-2.0
+diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
+index 476c9e34..bc192dc6 100644
+--- org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
++++ org.jacoco.core/src/org/jacoco/core/internal/instr/ClassInstrumenter.java
+@@ -24,6 +24,7 @@ import org.objectweb.asm.MethodVisitor;
+ public class ClassInstrumenter extends ClassProbesVisitor {
+
+ private final IProbeArrayStrategy probeArrayStrategy;
++ private final IProbeInserterFactory probeInserterFactory;
+
+ private String className;
+
+@@ -40,6 +41,22 @@ public class ClassInstrumenter extends ClassProbesVisitor {
+ final ClassVisitor cv) {
+ super(cv);
+ this.probeArrayStrategy = probeArrayStrategy;
++ this.probeInserterFactory = new IProbeInserterFactory() {
++ @Override
++ public ProbeInserter makeProbeInserter(int access, String name,
++ String desc, MethodVisitor mv,
++ IProbeArrayStrategy arrayStrategy) {
++ return new ProbeInserter(access, name, desc, mv, arrayStrategy);
++ }
++ };
++ }
++
++ public ClassInstrumenter(final IProbeArrayStrategy probeArrayStrategy,
++ final IProbeInserterFactory probeInserterFactory,
++ final ClassVisitor cv) {
++ super(cv);
++ this.probeArrayStrategy = probeArrayStrategy;
++ this.probeInserterFactory = probeInserterFactory;
+ }
+
+ @Override
+@@ -71,8 +88,9 @@ public class ClassInstrumenter extends ClassProbesVisitor {
+ return null;
+ }
+ final MethodVisitor frameEliminator = new DuplicateFrameEliminator(mv);
+- final ProbeInserter probeVariableInserter = new ProbeInserter(access,
+- name, desc, frameEliminator, probeArrayStrategy);
++ final ProbeInserter probeVariableInserter =
++ probeInserterFactory.makeProbeInserter(access, name, desc,
++ frameEliminator, probeArrayStrategy);
+ return new MethodInstrumenter(probeVariableInserter,
+ probeVariableInserter);
+ }
+diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserterFactory.java org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserterFactory.java
+new file mode 100644
+index 00000000..19c2a7e2
+--- /dev/null
++++ org.jacoco.core/src/org/jacoco/core/internal/instr/IProbeInserterFactory.java
+@@ -0,0 +1,8 @@
++package org.jacoco.core.internal.instr;
++
++import org.objectweb.asm.MethodVisitor;
++
++public interface IProbeInserterFactory {
++ ProbeInserter makeProbeInserter(int access, String name, String desc,
++ MethodVisitor mv, IProbeArrayStrategy arrayStrategy);
++}
+diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
+index 71808ac8..3df93f63 100644
+--- org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
++++ org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
+@@ -78,7 +78,7 @@ public final class InstrSupport {
+ * Data type of the field that stores coverage information for a class (
+ * <code>boolean[]</code>).
+ */
+- public static final String DATAFIELD_DESC = "[Z";
++ public static final String DATAFIELD_DESC = "java/nio/ByteBuffer";
+
+ // === Init Method ===
+
+diff --git org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
+index 0f5b99ff..ba5daa6d 100644
+--- org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
++++ org.jacoco.core/src/org/jacoco/core/internal/instr/ProbeInserter.java
+@@ -25,7 +25,7 @@ import org.objectweb.asm.TypePath;
+ * addition the probe array has to be retrieved at the beginning of the method
+ * and stored in a local variable.
+ */
+-class ProbeInserter extends MethodVisitor implements IProbeInserter {
++public class ProbeInserter extends MethodVisitor implements IProbeInserter {
+
+ private final IProbeArrayStrategy arrayStrategy;
+
+@@ -36,7 +36,7 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter {
+ private final boolean clinit;
+
+ /** Position of the inserted variable. */
+- private final int variable;
++ protected final int variable;
+
+ /** Maximum stack usage of the code to access the probe array. */
+ private int accessorStackSize;
+@@ -56,7 +56,7 @@ class ProbeInserter extends MethodVisitor implements IProbeInserter {
+ * callback to create the code that retrieves the reference to
+ * the probe array
+ */
+- ProbeInserter(final int access, final String name, final String desc,
++ public ProbeInserter(final int access, final String name, final String desc,
+ final MethodVisitor mv, final IProbeArrayStrategy arrayStrategy) {
+ super(InstrSupport.ASM_API_VERSION, mv);
+ this.clinit = InstrSupport.CLINIT_NAME.equals(name);
diff --git a/third_party/jacoco_internal.BUILD b/third_party/jacoco_internal.BUILD
new file mode 100644
index 00000000..9e6140a7
--- /dev/null
+++ b/third_party/jacoco_internal.BUILD
@@ -0,0 +1,18 @@
+java_library(
+ name = "jacoco_internal",
+ srcs = glob([
+ "org.jacoco.core/src/org/jacoco/core/**/*.java",
+ ]),
+ resources = glob([
+ "org.jacoco.core/src/org/jacoco/core/internal/flow/java_no_throw_methods_list.dat",
+ ]),
+ javacopts = [
+ "-Xep:EqualsHashCode:OFF",
+ ],
+ deps = [
+ "@jazzer_ow2_asm//:asm",
+ "@jazzer_ow2_asm//:asm_commons",
+ "@jazzer_ow2_asm//:asm_tree",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/third_party/libFuzzer.BUILD b/third_party/libFuzzer.BUILD
new file mode 100644
index 00000000..e8559936
--- /dev/null
+++ b/third_party/libFuzzer.BUILD
@@ -0,0 +1,27 @@
+# Based on https://github.com/llvm/llvm-project/blob/llvmorg-11.1.0/compiler-rt/lib/fuzzer/build.sh
+cc_library(
+ name = "libFuzzer",
+ srcs = glob([
+ "*.cpp",
+ ]),
+ hdrs = glob([
+ "*.h",
+ "*.def",
+ ]),
+ copts = select({
+ "@platforms//os:windows": [
+ "/Ox", # Optimize for speed.
+ "/Oy-", # Do not omit frame pointer.
+ "/std:c++17",
+ ],
+ "//conditions:default": [
+ "-g",
+ "-O2",
+ "-fno-omit-frame-pointer",
+ "-std=c++11",
+ ],
+ }),
+ alwayslink = True,
+ linkstatic = True,
+ visibility = ["//visibility:public"],
+)
diff --git a/third_party/libjpeg_turbo.BUILD b/third_party/libjpeg_turbo.BUILD
new file mode 100644
index 00000000..4621f862
--- /dev/null
+++ b/third_party/libjpeg_turbo.BUILD
@@ -0,0 +1,69 @@
+load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake")
+load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
+load("@bazel_skylib//rules:select_file.bzl", "select_file")
+
+filegroup(
+ name = "all_files",
+ srcs = glob(["**"]),
+)
+
+filegroup(
+ name = "java_files",
+ srcs = glob(["java/org/libjpegturbo/turbojpeg/*.java"]),
+)
+
+cc_import(
+ name = "libawt",
+ hdrs = [
+ "@local_jdk//:include/jawt.h",
+ ],
+ shared_library = "@local_jdk//:lib/libawt.so",
+)
+
+cmake(
+ name = "libjpeg_turbo",
+ cache_entries = {
+ "CMAKE_BUILD_TYPE": "Release",
+ "CMAKE_C_COMPILER": "clang",
+ "CMAKE_C_FLAGS": "-fsanitize=address,fuzzer-no-link",
+ "CMAKE_SHARED_LINKER_FLAGS": "-fsanitize=address,fuzzer-no-link",
+ "WITH_JAVA": "1",
+ },
+ lib_source = ":all_files",
+ out_shared_libs = [
+ "libjpeg.so",
+ "libturbojpeg.so",
+ ],
+ deps = [
+ ":libawt",
+ "@fmeum_rules_jni//jni",
+ ],
+)
+
+select_file(
+ name = "libturbojpeg_so",
+ srcs = ":libjpeg_turbo",
+ subpath = "lib/libturbojpeg.so",
+)
+
+copy_file(
+ name = "turbojpeg_native",
+ src = ":libturbojpeg_so",
+ out = "libturbojpeg.so",
+ visibility = ["//visibility:public"],
+)
+
+genrule(
+ name = "generate_jni_loader",
+ outs = ["TJLoader.java"],
+ cmd = "echo 'package org.libjpegturbo.turbojpeg; final class TJLoader { static void load() { System.loadLibrary(\"turbojpeg\"); }}' > $@",
+)
+
+java_library(
+ name = "turbojpeg_java",
+ srcs = [
+ ":generate_jni_loader",
+ ":java_files",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/third_party/typetools.BUILD b/third_party/typetools.BUILD
new file mode 100644
index 00000000..f94fb0dc
--- /dev/null
+++ b/third_party/typetools.BUILD
@@ -0,0 +1,7 @@
+java_library(
+ name = "typetools",
+ srcs = glob([
+ "src/main/java/net/jodah/typetools/*.java",
+ ]),
+ visibility = ["//visibility:public"],
+)