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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/main/java/com/code_intelligence/jazzer/agent/Agent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ fun installInternal(
dumpClassesDir: String = Opt.dumpClassesDir.get(),
additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes.get(),
) {
// Enable conditional hooks when custom hooks are used together with coverage reporting so
// that hooks can be disabled during coverage report generation at shutdown (see #878).
// Without this, any use of hooked classes during coverage dumping would trigger custom hook
// dispatch, causing NoClassDefFoundError when the hook class is no longer loadable.
val useConditionalHooks =
conditionalHooks ||
(
userHookNames.isNotEmpty() &&
(Opt.coverageDump.get().isNotEmpty() || Opt.coverageReport.get().isNotEmpty())
)

val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet()
check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" }
val customHookNames = allCustomHookNames - disabledHookNames.toSet()
Expand Down Expand Up @@ -136,7 +147,7 @@ fun installInternal(
instrumentationTypes,
includedHooks.hooks,
customHooks.hooks,
conditionalHooks,
useConditionalHooks,
customHooks.additionalHookClassNameGlobber,
coverageIdSynchronizer,
dumpClassesDirPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ public static void registerFatalFindingHandlerForJUnit(Consumer<Throwable> findi

private static void shutdown() {
if (!Opt.coverageDump.get().isEmpty() || !Opt.coverageReport.get().isEmpty()) {
JazzerInternal.hooksEnabled = false;
if (!Opt.coverageDump.get().isEmpty()) {
CoverageRecorder.dumpJacocoCoverage(Opt.coverageDump.get());
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/code_intelligence/jazzer/driver/Opt.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ public final class Opt {
public static final OptItem<Boolean> mergeInner = boolSetting("merge_inner", false, null);

// Whether hook instrumentation should add a check for JazzerInternal#hooksEnabled before
// executing hooks. Used to disable hooks during non-fuzz JUnit tests.
// executing hooks. Used to disable hooks during non-fuzz JUnit tests and during coverage
// report generation at shutdown.
public static final OptItem<Boolean> conditionalHooks =
boolSetting("conditional_hooks", false, null);

Expand Down
26 changes: 26 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,32 @@ java_fuzz_target_test(
],
)

java_binary(
name = "CoverageWithHooksFuzzerHooks",
srcs = ["src/test/java/com/example/CoverageWithHooksFuzzerHooks.java"],
create_executable = False,
deploy_manifest_lines = ["Jazzer-Hook-Classes: com.example.CoverageWithHooksFuzzerHooks"],
deps = ["//src/main/java/com/code_intelligence/jazzer/api:hooks"],
)

# Regression test for https://github.com/CodeIntelligence/jazzer/issues/878:
# Custom hooks must be disabled during coverage report generation to prevent
# NoClassDefFoundError when hooked classes are used and the hook class is no
# longer available.
java_fuzz_target_test(
name = "CoverageWithHooksFuzzer",
srcs = ["src/test/java/com/example/CoverageWithHooksFuzzer.java"],
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"],
fuzzer_args = [
"--coverage_report=coverage.txt",
"--instrumentation_includes=com.example.**",
],
hook_jar = "CoverageWithHooksFuzzerHooks_deploy.jar",
target_class = "com.example.CoverageWithHooksFuzzer",
verify_crash_input = False,
verify_crash_reproducer = False,
)

java_library(
name = "autofuzz_inner_class_target",
srcs = ["src/test/java/com/example/AutofuzzInnerClassTarget.java"],
Expand Down
62 changes: 62 additions & 0 deletions tests/src/test/java/com/example/CoverageWithHooksFuzzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2026 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.FuzzerSecurityIssueLow;
import java.util.ArrayList;

/**
* Regression test for https://github.com/CodeIntelligence/jazzer/issues/878.
*
* <p>When generating a coverage report at shutdown, any use of hooked method would trigger custom
* hook dispatch. If the hook class is no longer loadable at that point, the JVM throws
* NoClassDefFoundError.
*
* <p>This test verifies that hooks are disabled during coverage report generation by checking
* whether the hook's system property marker was set after the last fuzzer iteration. The shutdown
* sequence calls coverage report generation BEFORE fuzzerTearDown, so if hooks fire during report
* generation, the property will be set when fuzzerTearDown runs.
*/
public class CoverageWithHooksFuzzer {
public static void fuzzerTestOneInput(byte[] data) {
// Use ArrayList so the hook fires during fuzzing.
ArrayList<Byte> list = new ArrayList<>();
for (byte b : data) {
list.add(b);
}
// Verify the hook actually fired during this iteration.
if (!"true".equals(System.getProperty("jazzer.test.hook.called"))) {
throw new IllegalStateException("Hook did not fire during fuzzing");
}
// Clear the property after all ArrayList usage in this iteration.
// If hooks fire during coverage report generation (after the last iteration),
// the property will be set again.
System.clearProperty("jazzer.test.hook.called");
if (list.size() > 3) {
throw new FuzzerSecurityIssueLow("found enough bytes");
}
}

public static void fuzzerTearDown() {
// fuzzerTearDown is called AFTER coverage report generation in the shutdown sequence.
// If hooks were active during coverage report generation, use of hooked classes
// would have triggered our hook, setting the property.
if ("true".equals(System.getProperty("jazzer.test.hook.called"))) {
throw new IllegalStateException("Hook was called during coverage report generation");
}
}
}
36 changes: 36 additions & 0 deletions tests/src/test/java/com/example/CoverageWithHooksFuzzerHooks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2026 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;

/**
* Hook that targets ArrayList.&lt;init&gt; that sets a system property so that we can check in the
* fuzz test whether the hook is called or not.
*/
public class CoverageWithHooksFuzzerHooks {
@MethodHook(
type = HookType.AFTER,
targetClassName = "java.util.ArrayList",
targetMethod = "<init>")
public static void arrayListInitHook(
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
System.setProperty("jazzer.test.hook.called", "true");
}
}