Skip to content

Commit f056298

Browse files
fmeumBrian Lewis
andauthored
all: Rework Opt value handling (#767)
all: Rework Opt value handling This is a complete rewrite of the logic underlying `Opt`, with the following notable changes: * All options are now evaluated lazily, with safeguards ensuring that there can be no attempts to modify their defaults after they have been read once. This reduces the risk of accidentally locking in `Opt` and then reading stale values due to its previous reliance on static initializers. * There are now clear precedence rules for all options and every option can be set from all possible sources: manifest entries, env variables, system properties, configuration parameters, command-line arguments. In follow-up PRs, the new capabilities of `OptItem` (e.g. accumulating settings and JUnit configuration parameters as sources) will be used in Jazzer. Co-authored-by: Brian Lewis <[email protected]>
1 parent 7de3170 commit f056298

27 files changed

+922
-424
lines changed

docs/common.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
## Common options and workflows
22

3+
* [Passing arguments](#passing-arguments)
34
* [Reproducing a finding](#reproducing-a-finding)
45
* [Minimizing a crashing input](#minimizing-a-crashing-input)
56
* [Parallel execution](#parallel-execution)
67
* [Autofuzz mode](#autofuzz-mode)
78

89
<!-- Created by https://github.com/ekalinin/github-markdown-toc -->
910

11+
### Passing arguments
12+
13+
Jazzer provides many configuration settings. An up-to-date list can be found by running Jazzer with the `--help` flag.
14+
15+
The value of a setting item `some_opt` is obtained from the following sources in increasing order of precedence:
16+
17+
- the default value
18+
- `META-INF/MANIFEST.MF` attribute `Jazzer-Some-Opt` on the classpath
19+
- the `JAZZER_SOME_OPT` environment variable
20+
- the `jazzer.some_opt` system property
21+
- the `jazzer.some_opt` JUnit configuration parameter
22+
- the `--some_opt` CLI parameter
23+
1024
### Reproducing a finding
1125

1226
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:
@@ -55,4 +69,3 @@ Creating objects from fuzzer input can lead to many reported exceptions.
5569
Jazzer addresses this issue by ignoring exceptions that the target method declares to throw.
5670
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.
5771
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.**`).
58-

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ java_library(
118118
deps = [
119119
"//src/main/java/com/code_intelligence/jazzer/android:android_runtime",
120120
"//src/main/java/com/code_intelligence/jazzer/driver",
121+
"//src/main/java/com/code_intelligence/jazzer/driver:opt",
121122
"//src/main/java/com/code_intelligence/jazzer/runtime:constants",
122123
"//src/main/java/com/code_intelligence/jazzer/utils:log",
123124
"//src/main/java/com/code_intelligence/jazzer/utils:zip_utils",

src/main/java/com/code_intelligence/jazzer/Jazzer.java

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.code_intelligence.jazzer.android.AndroidRuntime;
2828
import com.code_intelligence.jazzer.driver.Driver;
29+
import com.code_intelligence.jazzer.driver.Opt;
2930
import com.code_intelligence.jazzer.utils.Log;
3031
import com.code_intelligence.jazzer.utils.ZipUtils;
3132
import com.github.fmeum.rules_jni.RulesJni;
@@ -42,7 +43,7 @@
4243
import java.nio.file.Paths;
4344
import java.nio.file.attribute.FileAttribute;
4445
import java.nio.file.attribute.PosixFilePermissions;
45-
import java.util.AbstractMap.SimpleEntry;
46+
import java.util.AbstractMap.SimpleImmutableEntry;
4647
import java.util.ArrayList;
4748
import java.util.Arrays;
4849
import java.util.HashMap;
@@ -80,16 +81,18 @@ private static void start(List<String> args) throws IOException, InterruptedExce
8081
// itself is "silenced" by redirecting System.out and/or System.err.
8182
Log.fixOutErr(System.out, System.err);
8283

83-
parseJazzerArgsToProperties(args);
84+
Opt.registerAndValidateCommandLineArgs(parseJazzerArgs(args));
85+
Opt.handleHelpAndVersionArgs();
8486

8587
// --asan and --ubsan imply --native by default, but --native can also be used by itself to fuzz
8688
// native libraries without sanitizers (e.g. to quickly grow a corpus).
87-
final boolean loadASan = Boolean.parseBoolean(System.getProperty("jazzer.asan", "false"));
88-
final boolean loadUBSan = Boolean.parseBoolean(System.getProperty("jazzer.ubsan", "false"));
89-
final boolean loadHWASan = Boolean.parseBoolean(System.getProperty("jazzer.hwasan", "false"));
90-
final boolean fuzzNative = Boolean.parseBoolean(
91-
System.getProperty("jazzer.native", Boolean.toString(loadASan || loadUBSan || loadHWASan)));
92-
if ((loadASan || loadUBSan || loadHWASan) && !fuzzNative) {
89+
final boolean loadASan = Opt.asan.get();
90+
final boolean loadUBSan = Opt.ubsan.get();
91+
final boolean loadHWASan = Opt.hwasan.get();
92+
final boolean needsNative = loadASan || loadUBSan || loadHWASan;
93+
Opt.fuzzNative.setIfDefault(needsNative);
94+
final boolean fuzzNative = Opt.fuzzNative.get();
95+
if (needsNative && !fuzzNative) {
9396
Log.error("--asan, --hwasan and --ubsan cannot be used without --native");
9497
exit(1);
9598
}
@@ -182,28 +185,28 @@ private static void start(List<String> args) throws IOException, InterruptedExce
182185
exit(processBuilder.start().waitFor());
183186
}
184187

185-
private static void parseJazzerArgsToProperties(List<String> args) {
186-
args.stream()
188+
private static List<Map.Entry<String, String>> parseJazzerArgs(List<String> args) {
189+
return args.stream()
187190
.filter(arg -> arg.startsWith("--"))
188191
.map(arg -> arg.substring("--".length()))
189192
// Filter out "--", which can be used to declare that all further arguments aren't libFuzzer
190193
// arguments.
191194
.filter(arg -> !arg.isEmpty())
192195
.map(Jazzer::parseSingleArg)
193-
.forEach(e -> System.setProperty("jazzer." + e.getKey(), e.getValue()));
196+
.collect(toList());
194197
}
195198

196-
private static SimpleEntry<String, String> parseSingleArg(String arg) {
199+
private static SimpleImmutableEntry<String, String> parseSingleArg(String arg) {
197200
String[] nameAndValue = arg.split("=", 2);
198201
if (nameAndValue.length == 2) {
199202
// Example: --keep_going=10 --> (keep_going, 10)
200-
return new SimpleEntry<>(nameAndValue[0], nameAndValue[1]);
203+
return new SimpleImmutableEntry<>(nameAndValue[0], nameAndValue[1]);
201204
} else if (nameAndValue[0].startsWith("no")) {
202205
// Example: --nohooks --> (hooks, "false")
203-
return new SimpleEntry<>(nameAndValue[0].substring("no".length()), "false");
206+
return new SimpleImmutableEntry<>(nameAndValue[0].substring("no".length()), "false");
204207
} else {
205208
// Example: --dedup --> (dedup, "true")
206-
return new SimpleEntry<>(nameAndValue[0], "true");
209+
return new SimpleImmutableEntry<>(nameAndValue[0], "true");
207210
}
208211
}
209212

@@ -280,9 +283,8 @@ private static Stream<String> javaBinaryArgs() throws IOException {
280283
Path agentPath =
281284
RulesJni.extractLibrary("android_native_agent", "/com/code_intelligence/jazzer/android");
282285

283-
String jazzerAgentPath = System.getProperty("jazzer.agent_path");
284-
String bootclassClassOverrides =
285-
System.getProperty("jazzer.android_bootpath_classes_overrides");
286+
String jazzerAgentPath = Opt.agentPath.get();
287+
String bootclassClassOverrides = Opt.androidBootclassClassesOverrides.get();
286288

287289
String jazzerBootstrapJarPath =
288290
"com/code_intelligence/jazzer/android/jazzer_bootstrap_android.jar";
@@ -484,7 +486,7 @@ private static boolean isPosix() {
484486

485487
private static String getAndroidRuntimeOptions() {
486488
List<String> validInitOptions = Arrays.asList("use_platform_libs", "use_none", "");
487-
String initOptString = System.getProperty("jazzer.android_init_options");
489+
String initOptString = Opt.androidInitOptions.get();
488490
if (!validInitOptions.contains(initOptString)) {
489491
Log.error("Invalid android_init_options set for Android Runtime.");
490492
exit(1);

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ fun install(instrumentation: Instrumentation) {
3535

3636
fun installInternal(
3737
instrumentation: Instrumentation,
38-
userHookNames: List<String> = findManifestCustomHookNames() + Opt.customHooks,
39-
disabledHookNames: List<String> = Opt.disabledHooks,
38+
userHookNames: List<String> = findManifestCustomHookNames() + Opt.customHooks.get(),
39+
disabledHookNames: List<String> = Opt.disabledHooks.get(),
4040
instrumentationIncludes: List<String> = Opt.instrumentationIncludes.get(),
4141
instrumentationExcludes: List<String> = Opt.instrumentationExcludes.get(),
4242
customHookIncludes: List<String> = Opt.customHookIncludes.get(),
4343
customHookExcludes: List<String> = Opt.customHookExcludes.get(),
44-
trace: List<String> = Opt.trace,
45-
idSyncFile: String? = Opt.idSyncFile,
46-
dumpClassesDir: String = Opt.dumpClassesDir,
47-
additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes,
44+
trace: List<String> = Opt.trace.get(),
45+
idSyncFile: String = Opt.idSyncFile.get(),
46+
dumpClassesDir: String = Opt.dumpClassesDir.get(),
47+
additionalClassesExcludes: List<String> = Opt.additionalClassesExcludes.get(),
4848
) {
4949
val allCustomHookNames = (Constants.SANITIZER_HOOK_NAMES + userHookNames).toSet()
5050
check(allCustomHookNames.isNotEmpty()) { "No hooks registered; expected at least the built-in hooks" }
@@ -79,7 +79,7 @@ fun installInternal(
7979
}
8080
}.toSet()
8181

82-
val idSyncFilePath = idSyncFile?.takeUnless { it.isEmpty() }?.let {
82+
val idSyncFilePath = idSyncFile.takeUnless { it.isEmpty() }?.let {
8383
Paths.get(it).also { path ->
8484
Log.info("Synchronizing coverage IDs in ${path.toAbsolutePath()}")
8585
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class RuntimeInstrumentor(
5959
classfileBuffer: ByteArray,
6060
): ByteArray? {
6161
var pathPrefix = ""
62-
if (!Opt.instrumentOnly.isEmpty() && protectionDomain != null) {
62+
if (Opt.instrumentOnly.get().isNotEmpty() && protectionDomain != null) {
6363
var outputPathPrefix = protectionDomain.getCodeSource().getLocation().getFile().toString()
6464
if (outputPathPrefix.isNotEmpty()) {
6565
if (outputPathPrefix.contains(File.separator)) {
@@ -208,7 +208,7 @@ class RuntimeInstrumentor(
208208
}
209209

210210
private fun instrument(internalClassName: String, bytecode: ByteArray, fullInstrumentation: Boolean): ByteArray {
211-
val classWithHooksEnabledField = if (Opt.conditionalHooks) {
211+
val classWithHooksEnabledField = if (Opt.conditionalHooks.get()) {
212212
// Let the hook instrumentation emit additional logic that checks the value of the
213213
// hooksEnabled field on this class and skips the hook if it is false.
214214
"com/code_intelligence/jazzer/runtime/JazzerInternal"

src/main/java/com/code_intelligence/jazzer/android/AndroidRuntime.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.code_intelligence.jazzer.android;
1616

17+
import com.code_intelligence.jazzer.driver.Opt;
1718
import com.code_intelligence.jazzer.utils.Log;
1819
import com.github.fmeum.rules_jni.RulesJni;
1920

@@ -55,7 +56,7 @@ public static String getClassPathsCommand() {
5556
* @return The string for LD_LIBRARY_PATH.
5657
*/
5758
public static String getLdLibraryPath() {
58-
String initOptString = System.getProperty("jazzer.android_init_options");
59+
String initOptString = Opt.androidInitOptions.get();
5960
if (initOptString.equals(DO_NOT_INITIALIZE) || initOptString.equals("")) {
6061
return FUZZ_DIR;
6162
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ java_jni_library(
9292
"//src/main/native/com/code_intelligence/jazzer/driver:__subpackages__",
9393
],
9494
deps = [
95+
"//src/main/java/com/code_intelligence/jazzer/driver:opt",
9596
"//src/main/java/com/code_intelligence/jazzer/utils:log",
9697
],
9798
)

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,30 @@ java_library(
148148
"OptParser.java",
149149
],
150150
visibility = [
151+
# Do not add //src/main/java/com/code_intelligence/jazzer/api to this list - it must
152+
# function even if it isn't running within Jazzer.
153+
"//src/main/java/com/code_intelligence/jazzer:__pkg__",
151154
"//src/main/java/com/code_intelligence/jazzer/agent:__pkg__",
155+
"//src/main/java/com/code_intelligence/jazzer/android:__pkg__",
152156
"//src/main/java/com/code_intelligence/jazzer/driver:__subpackages__",
153157
"//src/main/java/com/code_intelligence/jazzer/junit:__pkg__",
154158
"//src/test/java/com/code_intelligence/jazzer/driver:__subpackages__",
155159
],
156160
deps = [
161+
":opt_item",
157162
"//src/main/java/com/code_intelligence/jazzer:constants",
158163
"//src/main/java/com/code_intelligence/jazzer/utils:log",
159164
],
160165
)
161166

167+
java_library(
168+
name = "opt_item",
169+
srcs = ["OptItem.java"],
170+
visibility = [
171+
"//src/test/java/com/code_intelligence/jazzer/driver:__pkg__",
172+
],
173+
)
174+
162175
java_library(
163176
name = "recording_fuzzed_data_provider",
164177
srcs = ["RecordingFuzzedDataProvider.java"],

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

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,31 @@
3434
public class Driver {
3535
public static int start(List<String> args, boolean spawnsSubprocesses) throws IOException {
3636
if (IS_ANDROID) {
37-
if (!System.getProperty("jazzer.autofuzz", "").isEmpty()) {
37+
if (!Opt.autofuzz.get().isEmpty()) {
3838
Log.error("--autofuzz is not supported for Android");
3939
return 1;
4040
}
41-
if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) {
42-
Log.warn("--coverage_report is not supported for Android and has been disabled");
43-
System.clearProperty("jazzer.coverage_report");
41+
if (!Opt.coverageReport.get().isEmpty()) {
42+
Log.error("--coverage_report is not supported for Android and has been disabled");
43+
return 1;
4444
}
45-
if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) {
46-
Log.warn("--coverage_dump is not supported for Android and has been disabled");
47-
System.clearProperty("jazzer.coverage_dump");
45+
if (!Opt.coverageDump.get().isEmpty()) {
46+
Log.error("--coverage_dump is not supported for Android and has been disabled");
47+
return 1;
4848
}
4949
}
5050

5151
if (spawnsSubprocesses) {
52-
if (!System.getProperty("jazzer.coverage_report", "").isEmpty()) {
52+
if (!Opt.coverageReport.get().isEmpty()) {
5353
Log.warn("--coverage_report does not support parallel fuzzing and has been disabled");
54-
System.clearProperty("jazzer.coverage_report");
54+
return 1;
5555
}
56-
if (!System.getProperty("jazzer.coverage_dump", "").isEmpty()) {
56+
if (!Opt.coverageDump.get().isEmpty()) {
5757
Log.warn("--coverage_dump does not support parallel fuzzing and has been disabled");
58-
System.clearProperty("jazzer.coverage_dump");
58+
return 1;
5959
}
6060

61-
String idSyncFileArg = System.getProperty("jazzer.id_sync_file", "");
61+
String idSyncFileArg = Opt.idSyncFile.get();
6262
Path idSyncFile;
6363
if (idSyncFileArg.isEmpty()) {
6464
// Create an empty temporary file used for coverage ID synchronization and
@@ -84,7 +84,7 @@ public static int start(List<String> args, boolean spawnsSubprocesses) throws IO
8484
}
8585

8686
if (args.stream().anyMatch("-merge_inner=1" ::equals)) {
87-
System.setProperty("jazzer.internal.merge_inner", "true");
87+
Opt.mergeInner.setIfDefault(true);
8888
}
8989

9090
// Jazzer's hooks use deterministic randomness and thus require a seed. Search for the last
@@ -108,9 +108,12 @@ public static int start(List<String> args, boolean spawnsSubprocesses) throws IO
108108
args.add(getDefaultRssLimitMbArg());
109109
}
110110

111-
// Do not modify properties beyond this point, loading Opt locks in their values.
112-
if (!Opt.instrumentOnly.isEmpty()) {
113-
boolean instrumentationSuccess = OfflineInstrumentor.instrumentJars(Opt.instrumentOnly);
111+
if (!Opt.instrumentOnly.get().isEmpty()) {
112+
if (Opt.dumpClassesDir.get().isEmpty()) {
113+
Log.error("--dump_classes_dir must be set with --instrument_only");
114+
exit(1);
115+
}
116+
boolean instrumentationSuccess = OfflineInstrumentor.instrumentJars(Opt.instrumentOnly.get());
114117
if (!instrumentationSuccess) {
115118
exit(1);
116119
}
@@ -119,8 +122,8 @@ public static int start(List<String> args, boolean spawnsSubprocesses) throws IO
119122

120123
Driver.class.getClassLoader().setDefaultAssertionStatus(true);
121124

122-
if (!Opt.autofuzz.isEmpty()) {
123-
AgentInstaller.install(Opt.hooks);
125+
if (!Opt.autofuzz.get().isEmpty()) {
126+
AgentInstaller.install(Opt.hooks.get());
124127
FuzzTargetHolder.fuzzTarget = FuzzTargetHolder.AUTOFUZZ_FUZZ_TARGET;
125128
return FuzzTargetRunner.startLibFuzzer(args);
126129
}
@@ -142,7 +145,7 @@ public static int start(List<String> args, boolean spawnsSubprocesses) throws IO
142145

143146
// Installing the agent after the following "findFuzzTarget" leads to an asan error
144147
// in it on "Class.forName(targetClassName)", but only during native fuzzing.
145-
AgentInstaller.install(Opt.hooks);
148+
AgentInstaller.install(Opt.hooks.get());
146149
FuzzTargetHolder.fuzzTarget = FuzzTargetFinder.findFuzzTarget(targetClassName);
147150
return FuzzTargetRunner.startLibFuzzer(args);
148151
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class FuzzTargetFinder {
3838
private static final String FUZZER_TEAR_DOWN = "fuzzerTearDown";
3939

4040
static String findFuzzTargetClassName() {
41-
if (!Opt.targetClass.isEmpty()) {
42-
return Opt.targetClass;
41+
if (!Opt.targetClass.get().isEmpty()) {
42+
return Opt.targetClass.get();
4343
}
4444
if (IS_ANDROID) {
4545
// Fuzz target detection tools aren't supported on android
@@ -72,7 +72,7 @@ static FuzzTarget findFuzzTarget(String targetClassName) {
7272
// Finds the traditional static fuzzerTestOneInput fuzz target method.
7373
private static FuzzTarget findFuzzTargetByMethodName(Class<?> clazz) {
7474
Method fuzzTargetMethod;
75-
if (Opt.experimentalMutator) {
75+
if (Opt.experimentalMutator.get()) {
7676
List<Method> fuzzTargetMethods =
7777
Arrays.stream(clazz.getMethods())
7878
.filter(method -> "fuzzerTestOneInput".equals(method.getName()))
@@ -105,7 +105,7 @@ private static FuzzTarget findFuzzTargetByMethodName(Class<?> clazz) {
105105
Stream
106106
.of(targetPublicStaticMethod(clazz, FUZZER_INITIALIZE, String[].class)
107107
.map(init -> (Callable<Object>) () -> {
108-
init.invoke(null, (Object) Opt.targetArgs.toArray(new String[] {}));
108+
init.invoke(null, (Object) Opt.targetArgs.get().toArray(new String[] {}));
109109
return null;
110110
}),
111111
targetPublicStaticMethod(clazz, FUZZER_INITIALIZE)

0 commit comments

Comments
 (0)