Skip to content

Commit 4dcbb56

Browse files
committed
fix: disable custom hooks during coverage report generation
1 parent 945c0e4 commit 4dcbb56

File tree

6 files changed

+139
-2
lines changed

6 files changed

+139
-2
lines changed

src/main/java/com/code_intelligence/jazzer/agent/Agent.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ fun installInternal(
5050
dumpClassesDir: String = Opt.dumpClassesDir.get(),
5151
additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes.get(),
5252
) {
53+
// Enable conditional hooks when custom hooks are used together with coverage reporting so
54+
// that hooks can be disabled during coverage report generation at shutdown (see #878).
55+
// Without this, any use of hooked classes during coverage dumping would trigger custom hook
56+
// dispatch, causing NoClassDefFoundError when the hook class is no longer loadable.
57+
val useConditionalHooks =
58+
conditionalHooks ||
59+
(
60+
userHookNames.isNotEmpty() &&
61+
(Opt.coverageDump.get().isNotEmpty() || Opt.coverageReport.get().isNotEmpty())
62+
)
63+
5364
val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet()
5465
check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" }
5566
val customHookNames = allCustomHookNames - disabledHookNames.toSet()
@@ -136,7 +147,7 @@ fun installInternal(
136147
instrumentationTypes,
137148
includedHooks.hooks,
138149
customHooks.hooks,
139-
conditionalHooks,
150+
useConditionalHooks,
140151
customHooks.additionalHookClassNameGlobber,
141152
coverageIdSynchronizer,
142153
dumpClassesDirPath,

src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ public static void registerFatalFindingHandlerForJUnit(Consumer<Throwable> findi
461461

462462
private static void shutdown() {
463463
if (!Opt.coverageDump.get().isEmpty() || !Opt.coverageReport.get().isEmpty()) {
464+
JazzerInternal.hooksEnabled = false;
464465
if (!Opt.coverageDump.get().isEmpty()) {
465466
CoverageRecorder.dumpJacocoCoverage(Opt.coverageDump.get());
466467
}

src/main/java/com/code_intelligence/jazzer/driver/Opt.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ public final class Opt {
250250
public static final OptItem<Boolean> mergeInner = boolSetting("merge_inner", false, null);
251251

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

tests/BUILD.bazel

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,32 @@ java_fuzz_target_test(
124124
],
125125
)
126126

127+
java_binary(
128+
name = "CoverageWithHooksFuzzerHooks",
129+
srcs = ["src/test/java/com/example/CoverageWithHooksFuzzerHooks.java"],
130+
create_executable = False,
131+
deploy_manifest_lines = ["Jazzer-Hook-Classes: com.example.CoverageWithHooksFuzzerHooks"],
132+
deps = ["//src/main/java/com/code_intelligence/jazzer/api:hooks"],
133+
)
134+
135+
# Regression test for https://github.com/CodeIntelligence/jazzer/issues/878:
136+
# Custom hooks must be disabled during coverage report generation to prevent
137+
# NoClassDefFoundError when hooked classes are used and the hook class is no
138+
# longer available.
139+
java_fuzz_target_test(
140+
name = "CoverageWithHooksFuzzer",
141+
srcs = ["src/test/java/com/example/CoverageWithHooksFuzzer.java"],
142+
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"],
143+
fuzzer_args = [
144+
"--coverage_report=coverage.txt",
145+
"--instrumentation_includes=com.example.**",
146+
],
147+
hook_jar = "CoverageWithHooksFuzzerHooks_deploy.jar",
148+
target_class = "com.example.CoverageWithHooksFuzzer",
149+
verify_crash_input = False,
150+
verify_crash_reproducer = False,
151+
)
152+
127153
java_library(
128154
name = "autofuzz_inner_class_target",
129155
srcs = ["src/test/java/com/example/AutofuzzInnerClassTarget.java"],
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2026 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
20+
import java.util.ArrayList;
21+
22+
/**
23+
* Regression test for https://github.com/CodeIntelligence/jazzer/issues/878.
24+
*
25+
* <p>When generating a coverage report at shutdown, any use of hooked method would trigger custom
26+
* hook dispatch. If the hook class is no longer loadable at that point, the JVM throws
27+
* NoClassDefFoundError.
28+
*
29+
* <p>This test verifies that hooks are disabled during coverage report generation by checking
30+
* whether the hook's system property marker was set after the last fuzzer iteration. The shutdown
31+
* sequence calls coverage report generation BEFORE fuzzerTearDown, so if hooks fire during report
32+
* generation, the property will be set when fuzzerTearDown runs.
33+
*/
34+
public class CoverageWithHooksFuzzer {
35+
public static void fuzzerTestOneInput(byte[] data) {
36+
// Use ArrayList so the hook fires during fuzzing.
37+
ArrayList<Byte> list = new ArrayList<>();
38+
for (byte b : data) {
39+
list.add(b);
40+
}
41+
// Verify the hook actually fired during this iteration.
42+
if (!"true".equals(System.getProperty("jazzer.test.hook.called"))) {
43+
throw new IllegalStateException("Hook did not fire during fuzzing");
44+
}
45+
// Clear the property after all ArrayList usage in this iteration.
46+
// If hooks fire during coverage report generation (after the last iteration),
47+
// the property will be set again.
48+
System.clearProperty("jazzer.test.hook.called");
49+
if (list.size() > 3) {
50+
throw new FuzzerSecurityIssueLow("found enough bytes");
51+
}
52+
}
53+
54+
public static void fuzzerTearDown() {
55+
// fuzzerTearDown is called AFTER coverage report generation in the shutdown sequence.
56+
// If hooks were active during coverage report generation, use of hooked classes
57+
// would have triggered our hook, setting the property.
58+
if ("true".equals(System.getProperty("jazzer.test.hook.called"))) {
59+
throw new IllegalStateException("Hook was called during coverage report generation");
60+
}
61+
}
62+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2026 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.HookType;
20+
import com.code_intelligence.jazzer.api.MethodHook;
21+
import java.lang.invoke.MethodHandle;
22+
23+
/**
24+
* Hook that targets ArrayList.&lt;init&gt; that sets a system property so that we can check in the
25+
* fuzz test whether the hook is called or not.
26+
*/
27+
public class CoverageWithHooksFuzzerHooks {
28+
@MethodHook(
29+
type = HookType.AFTER,
30+
targetClassName = "java.util.ArrayList",
31+
targetMethod = "<init>")
32+
public static void arrayListInitHook(
33+
MethodHandle method, Object thisObject, Object[] arguments, int hookId, Object returnValue) {
34+
System.setProperty("jazzer.test.hook.called", "true");
35+
}
36+
}

0 commit comments

Comments
 (0)