From 70aec25a2b9b5ac85856b1650c510edc0626ad55 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 20 Jan 2025 01:20:00 +0100 Subject: [PATCH 01/28] TMP: Jazzer unchained initial --- .../jazzer/driver/FuzzTargetRunner.java | 25 +++++++++++++++++++ .../jazzer/junit/FuzzTestExecutor.java | 4 ++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 112c4a2df..2a926512e 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -442,6 +442,31 @@ public static int startLibFuzzer(List args) { args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][]::new)); } + public static int startUnchainedFuzzer(List args) { + if (mutator == null) { + Log.error("Mutator framework is required for unchained fuzzing"); + exit(1); + } + + // Native (Java) corpus management + // read corpus and deserialize using mutator + // get features from each input + // - coverage features + // - execution time + // - memory usage + // apply user-configurable selection fn (SEL) to keep inputs based on the features + // this fn is also used during fuzzing + + // Native (Java) fuzzing loop in a thread + // - select input from corpus using user-configurable fuzzing strategy fn (FUZ) + // - apply mutator to generate new input + // - run fuzz target with new input + // - collect features and apply SEL to keep or discard input + // - if input is kept, write out the corpus + // Also start timeout thread that kills the fuzzing thread after timeout + return 0; + } + public static void registerFatalFindingHandlerForJUnit(Consumer findingHandler) { FuzzTargetRunner.fatalFindingHandlerForJUnit = Objects.requireNonNull(findingHandler); } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 66befa431..673fabe0d 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -330,7 +330,9 @@ public Optional execute( ApiStatsHolder.apiStats = new ApiStatsInterval(); - int exitCode = FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); + // TODO select the fuzzer using options + // int exitCode = FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); + int exitCode = FuzzTargetRunner.startUnchainedFuzzer(libFuzzerArgs); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get(); From 54a3249c89200273a13c8778816448d3b14ec524 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 27 Jan 2025 22:46:13 +0100 Subject: [PATCH 02/28] TMP: Add isUnchained option to select the new Java fuzzer --- .../AgentConfiguringArgumentsProvider.java | 6 ++- .../jazzer/junit/FuzzTest.java | 2 + .../jazzer/junit/FuzzTestExecutor.java | 40 ++++++++++++++++--- .../jazzer/junit/FuzzTestExtensions.java | 6 ++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java index c912410e7..f37de94b1 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfiguringArgumentsProvider.java @@ -44,7 +44,11 @@ public Stream provideArguments(ExtensionContext extensionCo // ArgumentProviders need the bootstrap jar on the classpath and there may be no user-provided // ArgumentProviders to trigger the call in FuzzTestExtensions. FuzzTestExecutor.configureAndInstallAgent( - extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions(), dictionaryPath); + extensionContext, + fuzzTest.maxDuration(), + fuzzTest.maxExecutions(), + dictionaryPath, + fuzzTest.isUnchained()); return Stream.empty(); } } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java index aec397520..814820184 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java @@ -127,6 +127,8 @@ */ long maxExecutions() default 0; + boolean isUnchained() default false; + /** * Controls the JUnit lifecycle of fuzz tests during fuzzing. * diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 673fabe0d..4b69218ab 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -26,6 +26,7 @@ import com.code_intelligence.jazzer.driver.FuzzTargetHolder; import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.driver.Opt; +import com.code_intelligence.jazzer.driver.OptItem; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; @@ -58,10 +59,33 @@ class FuzzTestExecutor { private final List libFuzzerArgs; private final Optional javaSeedsDir; + // unchained args + private final boolean isUnchained; + private final List opts; private FuzzTestExecutor(List libFuzzerArgs, Optional javaSeedsDir) { this.libFuzzerArgs = libFuzzerArgs; this.javaSeedsDir = javaSeedsDir; + this.isUnchained = false; + this.opts = null; + } + + private FuzzTestExecutor(Optional javaSeedsDir, List fuzzerArgs) { + this.libFuzzerArgs = null; + this.opts = fuzzerArgs; + this.javaSeedsDir = javaSeedsDir; + this.isUnchained = true; + } + + public static FuzzTestExecutor prepareUnchained( + ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) + throws IOException { + if (!hasBeenPrepared.compareAndSet(false, true)) { + throw new FuzzTestConfigurationError( + "FuzzTestExecutor#prepare can only be called once per test run"); + } + List opts = null; + return new FuzzTestExecutor(Optional.empty(), opts); } public static FuzzTestExecutor prepare( @@ -260,14 +284,19 @@ static void configureAndInstallAgent( ExtensionContext extensionContext, String maxDuration, long maxExecutions, - Optional dictionaryPath) + Optional dictionaryPath, + boolean isUnchained) throws IOException { if (!agentInstalled.compareAndSet(false, true)) { return; } if (Utils.isFuzzing(extensionContext)) { FuzzTestExecutor executor = - prepare(extensionContext, maxDuration, maxExecutions, dictionaryPath); + isUnchained + ? FuzzTestExecutor.prepareUnchained( + extensionContext, maxDuration, maxExecutions, dictionaryPath) + : FuzzTestExecutor.prepare( + extensionContext, maxDuration, maxExecutions, dictionaryPath); extensionContext.getRoot().getStore(Namespace.GLOBAL).put(FuzzTestExecutor.class, executor); AgentConfigurator.forFuzzing(extensionContext); } else { @@ -330,9 +359,10 @@ public Optional execute( ApiStatsHolder.apiStats = new ApiStatsInterval(); - // TODO select the fuzzer using options - // int exitCode = FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); - int exitCode = FuzzTargetRunner.startUnchainedFuzzer(libFuzzerArgs); + int exitCode = + isUnchained + ? FuzzTargetRunner.startUnchainedFuzzer(libFuzzerArgs) + : FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get(); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index 7de39b2cd..69ba22152 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -60,7 +60,11 @@ public void interceptTestTemplateMethod( // as that provider isn't invoked before fuzz test executions for the arguments provided by // user-provided ArgumentsProviders ("Java seeds"). FuzzTestExecutor.configureAndInstallAgent( - extensionContext, fuzzTest.maxDuration(), fuzzTest.maxExecutions(), dictionaryPath); + extensionContext, + fuzzTest.maxDuration(), + fuzzTest.maxExecutions(), + dictionaryPath, + fuzzTest.isUnchained()); // Skip the invocation of the test method with the special arguments provided by // FuzzTestArgumentsProvider and start fuzzing instead. if (Utils.isMarkedInvocation(invocationContext)) { From f9888543b2adb9d6dd634761789198d3e5e3fb51 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 27 Jan 2025 22:59:31 +0100 Subject: [PATCH 03/28] TMP: forward correct correct args to unchained --- .../java/com/code_intelligence/jazzer/driver/BUILD.bazel | 1 + .../code_intelligence/jazzer/driver/FuzzTargetRunner.java | 2 +- .../code_intelligence/jazzer/junit/FuzzTestExecutor.java | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 6c77c2ac5..63f1fe96a 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -108,6 +108,7 @@ java_jni_library( ":fuzzed_data_provider_impl", ":lifecycle_methods_invoker", ":opt", + ":opt_item", ":recording_fuzzed_data_provider", ":reproducer_template", ":signal_handler", diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 2a926512e..a73c0456f 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -442,7 +442,7 @@ public static int startLibFuzzer(List args) { args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][]::new)); } - public static int startUnchainedFuzzer(List args) { + public static int startUnchainedFuzzer(List args) { if (mutator == null) { Log.error("Mutator framework is required for unchained fuzzing"); exit(1); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 4b69218ab..46b9517cf 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -61,18 +61,18 @@ class FuzzTestExecutor { private final Optional javaSeedsDir; // unchained args private final boolean isUnchained; - private final List opts; + private final List fuzzerArgs; private FuzzTestExecutor(List libFuzzerArgs, Optional javaSeedsDir) { this.libFuzzerArgs = libFuzzerArgs; this.javaSeedsDir = javaSeedsDir; this.isUnchained = false; - this.opts = null; + this.fuzzerArgs = null; } private FuzzTestExecutor(Optional javaSeedsDir, List fuzzerArgs) { this.libFuzzerArgs = null; - this.opts = fuzzerArgs; + this.fuzzerArgs = fuzzerArgs; this.javaSeedsDir = javaSeedsDir; this.isUnchained = true; } @@ -361,7 +361,7 @@ public Optional execute( int exitCode = isUnchained - ? FuzzTargetRunner.startUnchainedFuzzer(libFuzzerArgs) + ? FuzzTargetRunner.startUnchainedFuzzer(fuzzerArgs) : FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get(); From 186582d6776b02bbb6fcff2263d0c66eff80afa1 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 28 Jan 2025 22:33:15 +0100 Subject: [PATCH 04/28] pass args to unchained --- .../jazzer/driver/FuzzTargetRunner.java | 8 +- .../jazzer/junit/FuzzTestExecutor.java | 96 +++++++++++++++++-- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index a73c0456f..e5d62926f 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -48,6 +48,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -442,12 +443,17 @@ public static int startLibFuzzer(List args) { args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][]::new)); } - public static int startUnchainedFuzzer(List args) { + // TODO when done prototyping: use UnchainedOptions (or similar) to pass the arguments instead of a Map + public static int startUnchainedFuzzer(Map args) { if (mutator == null) { Log.error("Mutator framework is required for unchained fuzzing"); exit(1); } + // print all args + for (Map.Entry entry : args.entrySet()) { + System.err.println(entry.getKey() + ": " + entry.getValue()); + } // Native (Java) corpus management // read corpus and deserialize using mutator // get features from each input diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 46b9517cf..afe772f9b 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -26,7 +26,6 @@ import com.code_intelligence.jazzer.driver.FuzzTargetHolder; import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.driver.Opt; -import com.code_intelligence.jazzer.driver.OptItem; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; @@ -40,7 +39,9 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Base64; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -61,7 +62,7 @@ class FuzzTestExecutor { private final Optional javaSeedsDir; // unchained args private final boolean isUnchained; - private final List fuzzerArgs; + private final Map fuzzerArgs; private FuzzTestExecutor(List libFuzzerArgs, Optional javaSeedsDir) { this.libFuzzerArgs = libFuzzerArgs; @@ -70,10 +71,10 @@ private FuzzTestExecutor(List libFuzzerArgs, Optional javaSeedsDir this.fuzzerArgs = null; } - private FuzzTestExecutor(Optional javaSeedsDir, List fuzzerArgs) { + private FuzzTestExecutor(Map fuzzerArgs) { this.libFuzzerArgs = null; this.fuzzerArgs = fuzzerArgs; - this.javaSeedsDir = javaSeedsDir; + this.javaSeedsDir = (Optional) fuzzerArgs.get("corpus"); this.isUnchained = true; } @@ -82,10 +83,65 @@ public static FuzzTestExecutor prepareUnchained( throws IOException { if (!hasBeenPrepared.compareAndSet(false, true)) { throw new FuzzTestConfigurationError( - "FuzzTestExecutor#prepare can only be called once per test run"); + "FuzzTestExecutor#prepareUnchained can only be called once per test run"); + } + + List allSources = + AnnotationSupport.findRepeatableAnnotations( + context.getRequiredTestMethod(), ArgumentsSource.class); + // Non-empty as it always contains FuzzingArgumentsProvider. + ArgumentsSource lastSource = allSources.get(allSources.size() - 1); + // Ensure that our ArgumentsProviders run last so that we can record all the seeds generated by + // user-provided ones. + if (lastSource.value().getPackage() != FuzzTestExecutor.class.getPackage()) { + throw new FuzzTestConfigurationError( + "@FuzzTest must be the last annotation on a fuzz test," + + " but it came after the (meta-)annotation " + + lastSource); + } + + List originalFuzzerArgs = Utils.getLibFuzzerArgs(context); + Map fuzzerArgs = new HashMap<>(); + + // Add passed in corpus directories (and files) at the beginning of the arguments list. + // libFuzzer uses the first directory to store discovered inputs, whereas all others are + // only used to provide additional seeds and aren't written into. + List corpusFilesOrDirs = Utils.getCorpusFilesOrDirs(context); + + // When reproducing individual inputs, we must not add any corpus directories to the command + // line or libFuzzer will fail with "Not a directory: ...; exiting". + Optional javaSeedsDir; + if (!corpusFilesOrDirs.isEmpty() + && corpusFilesOrDirs.stream().map(Paths::get).allMatch(Files::isRegularFile)) { + javaSeedsDir = Optional.empty(); + } else { + // Only create the default generated corpus directory if it is used as the generated corpus + // directory, i.e., if the user didn't provide any custom corpus directories that come before + // it on the libFuzzer command line. + boolean createDefaultGeneratedCorpusDir = corpusFilesOrDirs.isEmpty(); + javaSeedsDir = + Optional.of( + addInputAndSeedDirs(context, corpusFilesOrDirs, createDefaultGeneratedCorpusDir)); } - List opts = null; - return new FuzzTestExecutor(Optional.empty(), opts); + + fuzzerArgs.put("corpus", javaSeedsDir); + + dictionaryPath.ifPresent(s -> fuzzerArgs.put("dictionary", s)); + + fuzzerArgs.put("maxDuration", durationStringToSeconds(maxDuration)); + if (maxRuns > 0) { + fuzzerArgs.put("maxRuns", maxRuns); + } + + if (Utils.permissivelyParseBoolean( + context.getConfigurationParameter("jazzer.valueprofile").orElse("false"))) { + fuzzerArgs.put("useValueProfile", true); + } + + translateJUnitTimeoutToSeconds(context) + .ifPresent(seconds -> fuzzerArgs.put("timeout", seconds)); + + return new FuzzTestExecutor(fuzzerArgs); } public static FuzzTestExecutor prepare( @@ -188,6 +244,32 @@ private static Optional translateJUnitTimeoutToLibFuzzerFlag(ExtensionCo .map(timeoutSeconds -> String.format("-timeout=%d", timeoutSeconds)); } + private static Optional translateJUnitTimeoutToSeconds(ExtensionContext context) { + return Stream.>>of( + () -> + AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), Timeout.class) + .map(timeout -> timeout.unit().toSeconds(timeout.value())), + () -> + AnnotationSupport.findAnnotation(context.getRequiredTestClass(), Timeout.class) + .map(timeout -> timeout.unit().toSeconds(timeout.value())), + () -> + context.getConfigurationParameter( + "junit.jupiter.execution.timeout.testtemplate.method.default", + Utils::parseJUnitTimeoutValueToSeconds), + () -> + context.getConfigurationParameter( + "junit.jupiter.execution.timeout.testable.method.default", + Utils::parseJUnitTimeoutValueToSeconds), + () -> + context.getConfigurationParameter( + "junit.jupiter.execution.timeout.default", + Utils::parseJUnitTimeoutValueToSeconds)) + .map(Supplier::get) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } + /** * Discovers and adds the directories for the generated corpus, Java seed corpus and findings to * the libFuzzer command line. From bbed1d2fac393c96de7d7784a9ed9bf91c6cf00d Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Wed, 29 Jan 2025 00:33:06 +0100 Subject: [PATCH 05/28] use mutator framework: problem - it uses libfuzzer that is now uninitialized --- .../jazzer/driver/FuzzTargetRunner.java | 82 ++++++++++++++++++- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index e5d62926f..b4a7b3232 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -443,7 +443,8 @@ public static int startLibFuzzer(List args) { args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][]::new)); } - // TODO when done prototyping: use UnchainedOptions (or similar) to pass the arguments instead of a Map + // TODO when done prototyping: use UnchainedOptions (or similar) to pass the arguments instead of + // a Map public static int startUnchainedFuzzer(Map args) { if (mutator == null) { Log.error("Mutator framework is required for unchained fuzzing"); @@ -454,7 +455,7 @@ public static int startUnchainedFuzzer(Map args) { for (Map.Entry entry : args.entrySet()) { System.err.println(entry.getKey() + ": " + entry.getValue()); } - // Native (Java) corpus management + // Native (Java) corpus management ### LATER // read corpus and deserialize using mutator // get features from each input // - coverage features @@ -463,13 +464,88 @@ public static int startUnchainedFuzzer(Map args) { // apply user-configurable selection fn (SEL) to keep inputs based on the features // this fn is also used during fuzzing - // Native (Java) fuzzing loop in a thread + // Mutation Core ## LATER + + // Native (Java) fuzzing loop in a thread ### NOW // - select input from corpus using user-configurable fuzzing strategy fn (FUZ) // - apply mutator to generate new input // - run fuzz target with new input // - collect features and apply SEL to keep or discard input // - if input is kept, write out the corpus // Also start timeout thread that kills the fuzzing thread after timeout + + // Extract some args. + // TODO: use UnchainedOptions instead + Integer maxExecutions = (Integer) args.get("maxExecutions"); + Long maxDuration = (Long) args.get("maxDuration"); + // if seed is null, use a random seed and print it + + // make byte arrey of size 1 containing "\n" + byte[] initialBuf = new byte[1]; + initialBuf[0] = '\n'; + + // corpus in RAM only for now + List corpus = new ArrayList<>(); + corpus.add(initialBuf); + + Thread fuzzerThread = + new Thread( + () -> { + Long seed = (Long) args.get("seed"); + if (seed == null) { + seed = (long) (Math.random() * Long.MAX_VALUE); + System.err.println("Seed: " + seed); + } + + // limited fuzzing loop for now + for (int i = 0; i < 10; i++) { + Throwable finding = null; + // select a random input from corpus + int index = (int) (Math.random() * corpus.size()); + mutator.read(new ByteArrayInputStream(corpus.get(index))); + mutator.mutate(seed); + + try { + lifecycleMethodsInvoker.beforeEachExecution(); + } catch (Throwable uncaughtFinding) { + finding = uncaughtFinding; + } + // Do not insert code here. After beforeEachExecution has completed without a + // finding, we should + // always enter the try block that calls afterEachExecution in finally. + if (finding == null) { + try { + Object fuzzTargetInstance = lifecycleMethodsInvoker.getTestClassInstance(); + // detaching because we mutate + mutator.invoke(fuzzTargetInstance, true); + } catch (Throwable uncaughtFinding) { + finding = uncaughtFinding; + } finally { + mutator.finishFuzzingIteration(); + try { + lifecycleMethodsInvoker.afterEachExecution(); + } catch (Throwable t) { + if (finding != null) { + // We already have a finding and do not know whether the fuzz target is in + // an expected + // state, so report this as a warning rather than an error or finding. + Log.warn("Failed to run lifecycle method", t); + } else { + finding = t; + } + } + } + } + seed++; + } + }); + fuzzerThread.start(); + try { + fuzzerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return 0; } From 73f2c6ecf303b3122263fbd57c00683f0d0cee73 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Wed, 29 Jan 2025 01:05:01 +0100 Subject: [PATCH 06/28] add TODO: resume from here --- .../jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java index 230ad82e9..05c5eb5b7 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java @@ -69,6 +69,7 @@ public static T mutateDefault(T value, Serializer serializer, int maxSize } private static int defaultMutate(byte[] buffer, int size) { + // TODO: implement byte [] mutator in Java and get rid of all JNI calls in the mutator framework, including this mock if (Mutator.SHOULD_MOCK) { return defaultMutateMock(buffer, size); } else { From 30fb634eed1be374f385c3d8971e01736aeffc9f Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 18 Feb 2025 00:43:49 +0100 Subject: [PATCH 07/28] TMP: Working on the Java byte[] mutator --- .../jazzer/driver/FuzzTargetRunner.java | 6 +- .../libfuzzer/LibFuzzerMutatorFactory.java | 66 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index b4a7b3232..7785f7d42 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -476,7 +476,7 @@ public static int startUnchainedFuzzer(Map args) { // Extract some args. // TODO: use UnchainedOptions instead - Integer maxExecutions = (Integer) args.get("maxExecutions"); + Long maxRuns = (Long) args.get("maxRuns"); Long maxDuration = (Long) args.get("maxDuration"); // if seed is null, use a random seed and print it @@ -498,7 +498,8 @@ public static int startUnchainedFuzzer(Map args) { } // limited fuzzing loop for now - for (int i = 0; i < 10; i++) { + long run = 0; + while (run < maxRuns) { Throwable finding = null; // select a random input from corpus int index = (int) (Math.random() * corpus.size()); @@ -537,6 +538,7 @@ public static int startUnchainedFuzzer(Map args) { } } seed++; + run++; } }); fuzzerThread.start(); diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index d2cc897e0..ea542794f 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -40,6 +40,18 @@ public final class LibFuzzerMutatorFactory { private static final int DEFAULT_MIN_LENGTH = 0; private static final int DEFAULT_MAX_LENGTH = 1000; + // interface to hold mutation functions + private interface MutationFunction { + byte[] mutate(byte[] value, int maxSizeIncrease, PseudoRandom prng); + } + + private static final MutationFunction[] MUTATION_FUNCTIONS = { + LibFuzzerMutator::deleteRandomChunk, + LibFuzzerMutator::insertRandomByte, + LibFuzzerMutator::insertRepeatedBytes, + LibFuzzerMutator::changeByte + }; + @CheckReturnValue public static Optional> tryCreate(AnnotatedType type) { Optional withLength = Optional.ofNullable(type.getAnnotation(WithLength.class)); @@ -114,8 +126,58 @@ private int maxInitialSize() { @Override public byte[] mutate(byte[] value, PseudoRandom prng) { int maxLengthIncrease = maxLength - value.length; - byte[] mutated = LibFuzzerMutate.mutateDefault(value, maxLengthIncrease); - return enforceLength(mutated); + return enforceLength(prng.pickIn(MUTATION_FUNCTIONS).mutate(value, maxLengthIncrease, prng)); + } + + public static byte[] changeByte(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (value.length == 0) { + return new byte[0]; + } + int pos = prng.indexIn(value.length); + byte[] out = Arrays.copyOf(value, value.length); + out[pos] = (byte) prng.closedRange(0, 255); + return out; + } + + public static byte[] deleteRandomChunk(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (value.length < 2) { + return new byte[0]; + } + int eraseN = prng.indexIn(value.length / 2) + 1; + int eraseStart = prng.indexIn(value.length - eraseN + 1); + byte[] out = new byte[value.length - eraseN]; + System.arraycopy(value, 0, out, 0, eraseStart); + System.arraycopy( + value, eraseStart + eraseN, out, eraseStart, value.length - eraseStart - eraseN); + return out; + } + + public static byte[] insertRandomByte(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (maxSizeIncrease == 0) { + return value; + } + int insertPos = prng.indexIn(value.length + 1); + byte[] out = new byte[value.length + 1]; + System.arraycopy(value, 0, out, 0, insertPos); + out[insertPos] = (byte) prng.closedRange(0, 255); + System.arraycopy(value, insertPos, out, insertPos + 1, value.length - insertPos); + return out; + } + + public static byte[] insertRepeatedBytes(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (maxSizeIncrease == 0) { + return value; + } + int insertPos = prng.indexIn(value.length + 1); + int insertSize = prng.closedRange(1, Math.min(maxSizeIncrease, 128)); + byte[] out = new byte[value.length + insertSize]; + System.arraycopy(value, 0, out, 0, insertPos); + byte repeatedByte = (byte) prng.closedRange(0, 255); + for (int i = 0; i < insertSize; i++) { + out[insertPos + i] = repeatedByte; + } + System.arraycopy(value, insertPos, out, insertPos + insertSize, value.length - insertPos); + return out; } private byte[] enforceLength(byte[] mutated) { From 263824811d1f80ac3ec4167540995decbde12200 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 1 Apr 2025 00:23:28 +0200 Subject: [PATCH 08/28] tmp: first tests for LibFuzzerMutatorFactory --- .../mutation/mutator/libfuzzer/BUILD.bazel | 1 + .../mutator/libfuzzer/LibFuzzerMutate.java | 3 +- .../mutation/mutator/libfuzzer/BUILD.bazel | 19 ++ .../LibFuzzerMutatorFactoryTest.java | 189 ++++++++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel create mode 100644 src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel index f08cea08f..a4cf1a22e 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel @@ -10,6 +10,7 @@ java_library( "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang:__pkg__", "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/lang:__subpackages__", "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/time:__pkg__", + "//src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer:__pkg__", ], deps = [ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java index 05c5eb5b7..fc23bbb61 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java @@ -69,7 +69,8 @@ public static T mutateDefault(T value, Serializer serializer, int maxSize } private static int defaultMutate(byte[] buffer, int size) { - // TODO: implement byte [] mutator in Java and get rid of all JNI calls in the mutator framework, including this mock + // TODO: implement byte [] mutator in Java and get rid of all JNI calls in the mutator + // framework, including this mock if (Mutator.SHOULD_MOCK) { return defaultMutateMock(buffer, size); } else { diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel new file mode 100644 index 000000000..34bc1b47b --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel @@ -0,0 +1,19 @@ +load("@contrib_rules_jvm//java:defs.bzl", "java_test_suite") + +java_test_suite( + name = "libfuzzer", + size = "small", + srcs = glob(["*.java"]), + # env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"}, + runner = "junit5", + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/engine", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer:libfuzzermutate", + "//src/main/java/com/code_intelligence/jazzer/mutation/support", + "//src/test/java/com/code_intelligence/jazzer/mutation/support:test_support", + "@protobuf//java/core", + ], +) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java new file mode 100644 index 000000000..47331b0b0 --- /dev/null +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -0,0 +1,189 @@ +/* + * Copyright 2024 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.mutation.mutator.libfuzzer; + +import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.code_intelligence.jazzer.mutation.annotation.NotNull; +import com.code_intelligence.jazzer.mutation.annotation.WithLength; +import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; +import com.code_intelligence.jazzer.mutation.support.TypeHolder; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +class LibFuzzerMutatorFactoryTest { + + @Test + void testInit() { + Optional> opt = + LibFuzzerMutatorFactory.tryCreate( + new TypeHolder() {}.annotatedType()); + assertThat(opt).isPresent(); + SerializingMutator mutator = (SerializingMutator) opt.get(); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = mockPseudoRandom(4, new byte[] {0, 1, 2, 3})) { + byte[] a = mutator.init(prng); + assertThat(a).isEqualTo(new byte[] {0, 1, 2, 3}); + } + } + + @Test + void testEraseByteEvenSize() { + Optional> opt = + LibFuzzerMutatorFactory.tryCreate( + new TypeHolder() {}.annotatedType()); + assertThat(opt).isPresent(); + SerializingMutator mutator = (SerializingMutator) opt.get(); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = + mockPseudoRandom( + // 0: op - erase byte + // 1: Number of bytes to erase (+1) + // 2: start index + // Erase the first byte + 0, + 0, + 0, + // Erase the first byte + 0, + 0, + 0, + // Erase the second byte + 0, + 0, + 1, + // Erase the third byte + 0, + 0, + 2, + // Erase the fourth byte + 0, + 0, + 3, + // Erase the first two bytes + 0, + 1, + 0, + // Erase the second two bytes + 0, + 1, + 1, + // Erase the last two bytes + 0, + 1, + 2, + // Erase the first three bytes should fail + 0, + 2)) { + + final byte[] data = new byte[] {0, 1, 2, 3}; + + // One byte + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {1, 2, 3}); + assertThat(mutator.mutate(new byte[] {0, 10, 20, 30}, prng)) + .isEqualTo(new byte[] {10, 20, 30}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 2, 3}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 3}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2}); + + // Two bytes + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {2, 3}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 3}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1}); + + assertThrows(AssertionError.class, () -> mutator.mutate(data, prng)); + } + } + + @Test + void testEraseByteOddSize() { + Optional> opt = + LibFuzzerMutatorFactory.tryCreate( + new TypeHolder() {}.annotatedType()); + assertThat(opt).isPresent(); + SerializingMutator mutator = (SerializingMutator) opt.get(); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = + mockPseudoRandom( + // 0: op - erase byte + // 1: Number of bytes to erase (+1) + // 2: start index + // Erase the first byte + 0, + 0, + 0, + // Erase the first byte + 0, + 0, + 0, + // Erase the second byte + 0, + 0, + 1, + // Erase the third byte + 0, + 0, + 2, + // Erase the fourth byte + 0, + 0, + 3, + // Erase the first two bytes + 0, + 1, + 0, + // Erase the second two bytes + 0, + 1, + 1, + // Erase the butlast two bytes + 0, + 1, + 2, + // Erase the last two bytes + 0, + 1, + 3, + // Erase the first three bytes should fail + 0, + 2)) { + final byte[] data = new byte[] {0, 1, 2, 3, 4}; + + // One byte + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {1, 2, 3, 4}); + assertThat(mutator.mutate(new byte[] {0, 10, 20, 30, 40}, prng)) + .isEqualTo(new byte[] {10, 20, 30, 40}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 2, 3, 4}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 3, 4}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2, 4}); + + // Two bytes + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {2, 3, 4}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 3, 4}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 4}); + assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2}); + + assertThrows(AssertionError.class, () -> mutator.mutate(data, prng)); + } + } +} From 84dd81175cbd6760f9b9144e3cfd8790e593ca18 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 1 Apr 2025 08:26:54 +0200 Subject: [PATCH 09/28] TMP: add insert byte test --- .../LibFuzzerMutatorFactoryTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index 47331b0b0..04420cd79 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -186,4 +186,76 @@ void testEraseByteOddSize() { assertThrows(AssertionError.class, () -> mutator.mutate(data, prng)); } } + + @Test + void testInsertRandomByte() { + Optional> opt = + LibFuzzerMutatorFactory.tryCreate( + new TypeHolder() {}.annotatedType()); + assertThat(opt).isPresent(); + SerializingMutator mutator = (SerializingMutator) opt.get(); + assertThat(mutator.toString()).isEqualTo("byte[]"); + + try (MockPseudoRandom prng = + mockPseudoRandom( + // 0: op - insertRandomByte + // 1: Index to insert at + // 2: Byte to insert + // Insert 2 at index 0 + 1, + 0, + 2, + // Insert 127 at index 1 + 1, + 1, + 127, + // Insert 2 at index 2 + 1, + 2, + 2, + // Insert 2 at index 1 + 1, + 1, + 2, + // Insert 2 at index 0 + 1, + 0, + 2, + // Insert 2 at index 3 + 1, + 3, + 30, + // Insert 2 at index 2 + 1, + 2, + 40, + // Insert 2 at index 1 + 1, + 1, + 50, + // Insert 2 at index 0 + 1, + 0, + 60)) { + + final byte[] data1 = new byte[] {0}; + + // One byte + assertThat(mutator.mutate(data1, prng)).isEqualTo(new byte[] {2, 0}); + assertThat(mutator.mutate(data1, prng)).isEqualTo(new byte[] {0, 127}); + + // Two bytes + final byte[] data2 = new byte[] {0, 1}; + assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {0, 1, 2}); + assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {0, 2, 1}); + assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {2, 0, 1}); + + // Three bytes + final byte[] data3 = new byte[] {0, 1, 2}; + assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 1, 2, 30}); + assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 1, 40, 2}); + assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 50, 1, 2}); + assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {60, 0, 1, 2}); + } + } } From 85b2325e4ec5a8b0cf9c3faaabf570a46cc0a213 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 1 Apr 2025 09:05:00 +0200 Subject: [PATCH 10/28] TMP: convert tests to parameterized --- .../LibFuzzerMutatorFactoryTest.java | 256 ++++-------------- 1 file changed, 54 insertions(+), 202 deletions(-) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index 04420cd79..771cfe493 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -17,7 +17,7 @@ import static com.code_intelligence.jazzer.mutation.support.TestSupport.mockPseudoRandom; import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.Arguments.arguments; import com.code_intelligence.jazzer.mutation.annotation.NotNull; import com.code_intelligence.jazzer.mutation.annotation.WithLength; @@ -25,9 +25,12 @@ import com.code_intelligence.jazzer.mutation.support.TestSupport.MockPseudoRandom; import com.code_intelligence.jazzer.mutation.support.TypeHolder; import java.util.Optional; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -@SuppressWarnings("unchecked") class LibFuzzerMutatorFactoryTest { @Test @@ -45,150 +48,57 @@ void testInit() { } } - @Test - void testEraseByteEvenSize() { - Optional> opt = - LibFuzzerMutatorFactory.tryCreate( - new TypeHolder() {}.annotatedType()); - assertThat(opt).isPresent(); - SerializingMutator mutator = (SerializingMutator) opt.get(); - assertThat(mutator.toString()).isEqualTo("byte[]"); - - try (MockPseudoRandom prng = - mockPseudoRandom( - // 0: op - erase byte - // 1: Number of bytes to erase (+1) - // 2: start index - // Erase the first byte - 0, - 0, - 0, - // Erase the first byte - 0, - 0, - 0, - // Erase the second byte - 0, - 0, - 1, - // Erase the third byte - 0, - 0, - 2, - // Erase the fourth byte - 0, - 0, - 3, - // Erase the first two bytes - 0, - 1, - 0, - // Erase the second two bytes - 0, - 1, - 1, - // Erase the last two bytes - 0, - 1, - 2, - // Erase the first three bytes should fail - 0, - 2)) { - - final byte[] data = new byte[] {0, 1, 2, 3}; - - // One byte - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {1, 2, 3}); - assertThat(mutator.mutate(new byte[] {0, 10, 20, 30}, prng)) - .isEqualTo(new byte[] {10, 20, 30}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 2, 3}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 3}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2}); - - // Two bytes - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {2, 3}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 3}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1}); - - assertThrows(AssertionError.class, () -> mutator.mutate(data, prng)); - } + static final int DEL_CHUNK = 0; + static final int INS_BYTE = 1; + static final int INS_REP = 2; + static final int MUT_BYTE = 3; + + static Stream deleteChunk() { + final byte[] even = new byte[] {0, 1, 2, 3}; + final byte[] odd = new byte[] {0, 1, 2, 3, 4}; + return Stream.of( + // Even length, delete one byte + arguments(DEL_CHUNK, 0, 0, even, new byte[] {1, 2, 3}), + arguments(DEL_CHUNK, 0, 0, new byte[] {0, 10, 20, 30}, new byte[] {10, 20, 30}), + arguments(DEL_CHUNK, 0, 1, even, new byte[] {0, 2, 3}), + arguments(DEL_CHUNK, 0, 2, even, new byte[] {0, 1, 3}), + arguments(DEL_CHUNK, 0, 3, even, new byte[] {0, 1, 2}), + // Delete two bytes + arguments(DEL_CHUNK, 1, 0, even, new byte[] {2, 3}), + arguments(DEL_CHUNK, 1, 1, even, new byte[] {0, 3}), + arguments(DEL_CHUNK, 1, 2, even, new byte[] {0, 1}), + // Odd length, delete one byte + arguments(DEL_CHUNK, 0, 0, odd, new byte[] {1, 2, 3, 4}), + arguments(DEL_CHUNK, 0, 1, odd, new byte[] {0, 2, 3, 4}), + arguments(DEL_CHUNK, 0, 2, odd, new byte[] {0, 1, 3, 4}), + arguments(DEL_CHUNK, 0, 3, odd, new byte[] {0, 1, 2, 4}), + arguments(DEL_CHUNK, 0, 4, odd, new byte[] {0, 1, 2, 3}), + // Delete two bytes + arguments(DEL_CHUNK, 1, 0, odd, new byte[] {2, 3, 4}), + arguments(DEL_CHUNK, 1, 1, odd, new byte[] {0, 3, 4}), + arguments(DEL_CHUNK, 1, 2, odd, new byte[] {0, 1, 4}), + arguments(DEL_CHUNK, 1, 3, odd, new byte[] {0, 1, 2})); } - @Test - void testEraseByteOddSize() { - Optional> opt = - LibFuzzerMutatorFactory.tryCreate( - new TypeHolder() {}.annotatedType()); - assertThat(opt).isPresent(); - SerializingMutator mutator = (SerializingMutator) opt.get(); - assertThat(mutator.toString()).isEqualTo("byte[]"); - - try (MockPseudoRandom prng = - mockPseudoRandom( - // 0: op - erase byte - // 1: Number of bytes to erase (+1) - // 2: start index - // Erase the first byte - 0, - 0, - 0, - // Erase the first byte - 0, - 0, - 0, - // Erase the second byte - 0, - 0, - 1, - // Erase the third byte - 0, - 0, - 2, - // Erase the fourth byte - 0, - 0, - 3, - // Erase the first two bytes - 0, - 1, - 0, - // Erase the second two bytes - 0, - 1, - 1, - // Erase the butlast two bytes - 0, - 1, - 2, - // Erase the last two bytes - 0, - 1, - 3, - // Erase the first three bytes should fail - 0, - 2)) { - final byte[] data = new byte[] {0, 1, 2, 3, 4}; - - // One byte - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {1, 2, 3, 4}); - assertThat(mutator.mutate(new byte[] {0, 10, 20, 30, 40}, prng)) - .isEqualTo(new byte[] {10, 20, 30, 40}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 2, 3, 4}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 3, 4}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2, 4}); - - // Two bytes - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {2, 3, 4}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 3, 4}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 4}); - assertThat(mutator.mutate(data, prng)).isEqualTo(new byte[] {0, 1, 2}); - - assertThrows(AssertionError.class, () -> mutator.mutate(data, prng)); - } + static Stream insertByte() { + final byte[] input1 = new byte[] {0}; + final byte[] input2 = new byte[] {0, 1}; + final byte[] input3 = new byte[] {0, 1, 2}; + return Stream.of( + arguments(INS_BYTE, 0, 10, input1, new byte[] {10, 0}), + arguments(INS_BYTE, 1, 20, input1, new byte[] {0, 20}), + arguments(INS_BYTE, 0, 10, input2, new byte[] {10, 0, 1}), + arguments(INS_BYTE, 1, 20, input2, new byte[] {0, 20, 1}), + arguments(INS_BYTE, 2, 30, input2, new byte[] {0, 1, 30}), + arguments(INS_BYTE, 0, 10, input3, new byte[] {10, 0, 1, 2}), + arguments(INS_BYTE, 1, 20, input3, new byte[] {0, 20, 1, 2}), + arguments(INS_BYTE, 2, 30, input3, new byte[] {0, 1, 30, 2}), + arguments(INS_BYTE, 3, 40, input3, new byte[] {0, 1, 2, 40})); } - @Test - void testInsertRandomByte() { + @ParameterizedTest + @MethodSource({"deleteChunk", "insertByte"}) + void testMutatorOperations(int op, int arg1, int arg2, byte[] input, byte[] expected) { Optional> opt = LibFuzzerMutatorFactory.tryCreate( new TypeHolder() {}.annotatedType()); @@ -196,66 +106,8 @@ void testInsertRandomByte() { SerializingMutator mutator = (SerializingMutator) opt.get(); assertThat(mutator.toString()).isEqualTo("byte[]"); - try (MockPseudoRandom prng = - mockPseudoRandom( - // 0: op - insertRandomByte - // 1: Index to insert at - // 2: Byte to insert - // Insert 2 at index 0 - 1, - 0, - 2, - // Insert 127 at index 1 - 1, - 1, - 127, - // Insert 2 at index 2 - 1, - 2, - 2, - // Insert 2 at index 1 - 1, - 1, - 2, - // Insert 2 at index 0 - 1, - 0, - 2, - // Insert 2 at index 3 - 1, - 3, - 30, - // Insert 2 at index 2 - 1, - 2, - 40, - // Insert 2 at index 1 - 1, - 1, - 50, - // Insert 2 at index 0 - 1, - 0, - 60)) { - - final byte[] data1 = new byte[] {0}; - - // One byte - assertThat(mutator.mutate(data1, prng)).isEqualTo(new byte[] {2, 0}); - assertThat(mutator.mutate(data1, prng)).isEqualTo(new byte[] {0, 127}); - - // Two bytes - final byte[] data2 = new byte[] {0, 1}; - assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {0, 1, 2}); - assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {0, 2, 1}); - assertThat(mutator.mutate(data2, prng)).isEqualTo(new byte[] {2, 0, 1}); - - // Three bytes - final byte[] data3 = new byte[] {0, 1, 2}; - assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 1, 2, 30}); - assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 1, 40, 2}); - assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {0, 50, 1, 2}); - assertThat(mutator.mutate(data3, prng)).isEqualTo(new byte[] {60, 0, 1, 2}); + try (MockPseudoRandom prng = mockPseudoRandom(op, arg1, arg2)) { + assertThat(mutator.mutate(input, prng)).isEqualTo(expected); } } } From 292db420bd3fb2d05257aa767bb6381a2be32216 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 1 Apr 2025 09:25:37 +0200 Subject: [PATCH 11/28] TMP: allow variable number of args to the mock PRNG in the tests --- .../LibFuzzerMutatorFactoryTest.java | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index 771cfe493..9b61c7946 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -58,26 +58,26 @@ static Stream deleteChunk() { final byte[] odd = new byte[] {0, 1, 2, 3, 4}; return Stream.of( // Even length, delete one byte - arguments(DEL_CHUNK, 0, 0, even, new byte[] {1, 2, 3}), - arguments(DEL_CHUNK, 0, 0, new byte[] {0, 10, 20, 30}, new byte[] {10, 20, 30}), - arguments(DEL_CHUNK, 0, 1, even, new byte[] {0, 2, 3}), - arguments(DEL_CHUNK, 0, 2, even, new byte[] {0, 1, 3}), - arguments(DEL_CHUNK, 0, 3, even, new byte[] {0, 1, 2}), + arguments(arguments(DEL_CHUNK, 0, 0), even, new byte[] {1, 2, 3}), + arguments(arguments(DEL_CHUNK, 0, 0), new byte[] {0, 10, 20, 30}, new byte[] {10, 20, 30}), + arguments(arguments(DEL_CHUNK, 0, 1), even, new byte[] {0, 2, 3}), + arguments(arguments(DEL_CHUNK, 0, 2), even, new byte[] {0, 1, 3}), + arguments(arguments(DEL_CHUNK, 0, 3), even, new byte[] {0, 1, 2}), // Delete two bytes - arguments(DEL_CHUNK, 1, 0, even, new byte[] {2, 3}), - arguments(DEL_CHUNK, 1, 1, even, new byte[] {0, 3}), - arguments(DEL_CHUNK, 1, 2, even, new byte[] {0, 1}), + arguments(arguments(DEL_CHUNK, 1, 0), even, new byte[] {2, 3}), + arguments(arguments(DEL_CHUNK, 1, 1), even, new byte[] {0, 3}), + arguments(arguments(DEL_CHUNK, 1, 2), even, new byte[] {0, 1}), // Odd length, delete one byte - arguments(DEL_CHUNK, 0, 0, odd, new byte[] {1, 2, 3, 4}), - arguments(DEL_CHUNK, 0, 1, odd, new byte[] {0, 2, 3, 4}), - arguments(DEL_CHUNK, 0, 2, odd, new byte[] {0, 1, 3, 4}), - arguments(DEL_CHUNK, 0, 3, odd, new byte[] {0, 1, 2, 4}), - arguments(DEL_CHUNK, 0, 4, odd, new byte[] {0, 1, 2, 3}), + arguments(arguments(DEL_CHUNK, 0, 0), odd, new byte[] {1, 2, 3, 4}), + arguments(arguments(DEL_CHUNK, 0, 1), odd, new byte[] {0, 2, 3, 4}), + arguments(arguments(DEL_CHUNK, 0, 2), odd, new byte[] {0, 1, 3, 4}), + arguments(arguments(DEL_CHUNK, 0, 3), odd, new byte[] {0, 1, 2, 4}), + arguments(arguments(DEL_CHUNK, 0, 4), odd, new byte[] {0, 1, 2, 3}), // Delete two bytes - arguments(DEL_CHUNK, 1, 0, odd, new byte[] {2, 3, 4}), - arguments(DEL_CHUNK, 1, 1, odd, new byte[] {0, 3, 4}), - arguments(DEL_CHUNK, 1, 2, odd, new byte[] {0, 1, 4}), - arguments(DEL_CHUNK, 1, 3, odd, new byte[] {0, 1, 2})); + arguments(arguments(DEL_CHUNK, 1, 0), odd, new byte[] {2, 3, 4}), + arguments(arguments(DEL_CHUNK, 1, 1), odd, new byte[] {0, 3, 4}), + arguments(arguments(DEL_CHUNK, 1, 2), odd, new byte[] {0, 1, 4}), + arguments(arguments(DEL_CHUNK, 1, 3), odd, new byte[] {0, 1, 2})); } static Stream insertByte() { @@ -85,20 +85,20 @@ static Stream insertByte() { final byte[] input2 = new byte[] {0, 1}; final byte[] input3 = new byte[] {0, 1, 2}; return Stream.of( - arguments(INS_BYTE, 0, 10, input1, new byte[] {10, 0}), - arguments(INS_BYTE, 1, 20, input1, new byte[] {0, 20}), - arguments(INS_BYTE, 0, 10, input2, new byte[] {10, 0, 1}), - arguments(INS_BYTE, 1, 20, input2, new byte[] {0, 20, 1}), - arguments(INS_BYTE, 2, 30, input2, new byte[] {0, 1, 30}), - arguments(INS_BYTE, 0, 10, input3, new byte[] {10, 0, 1, 2}), - arguments(INS_BYTE, 1, 20, input3, new byte[] {0, 20, 1, 2}), - arguments(INS_BYTE, 2, 30, input3, new byte[] {0, 1, 30, 2}), - arguments(INS_BYTE, 3, 40, input3, new byte[] {0, 1, 2, 40})); + arguments(arguments(INS_BYTE, 0, 10), input1, new byte[] {10, 0}), + arguments(arguments(INS_BYTE, 1, 20), input1, new byte[] {0, 20}), + arguments(arguments(INS_BYTE, 0, 10), input2, new byte[] {10, 0, 1}), + arguments(arguments(INS_BYTE, 1, 20), input2, new byte[] {0, 20, 1}), + arguments(arguments(INS_BYTE, 2, 30), input2, new byte[] {0, 1, 30}), + arguments(arguments(INS_BYTE, 0, 10), input3, new byte[] {10, 0, 1, 2}), + arguments(arguments(INS_BYTE, 1, 20), input3, new byte[] {0, 20, 1, 2}), + arguments(arguments(INS_BYTE, 2, 30), input3, new byte[] {0, 1, 30, 2}), + arguments(arguments(INS_BYTE, 3, 40), input3, new byte[] {0, 1, 2, 40})); } @ParameterizedTest - @MethodSource({"deleteChunk", "insertByte"}) - void testMutatorOperations(int op, int arg1, int arg2, byte[] input, byte[] expected) { + @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes"}) + void testMutatorOperations(Arguments args, byte[] input, byte[] expected) { Optional> opt = LibFuzzerMutatorFactory.tryCreate( new TypeHolder() {}.annotatedType()); @@ -106,7 +106,8 @@ void testMutatorOperations(int op, int arg1, int arg2, byte[] input, byte[] expe SerializingMutator mutator = (SerializingMutator) opt.get(); assertThat(mutator.toString()).isEqualTo("byte[]"); - try (MockPseudoRandom prng = mockPseudoRandom(op, arg1, arg2)) { + // apply "args" to mockPseudoRandom + try (MockPseudoRandom prng = mockPseudoRandom(args.get())) { assertThat(mutator.mutate(input, prng)).isEqualTo(expected); } } From 11afbf10939fb805fa41a60579416902cc3fe2ef Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Tue, 1 Apr 2025 09:29:01 +0200 Subject: [PATCH 12/28] TMP: test insert repeated --- .../libfuzzer/LibFuzzerMutatorFactoryTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index 9b61c7946..b5a14261c 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -96,6 +96,24 @@ static Stream insertByte() { arguments(arguments(INS_BYTE, 3, 40), input3, new byte[] {0, 1, 2, 40})); } + static Stream insertRepeatedBytes() { + final byte[] input1 = new byte[] {0}; + final byte[] input2 = new byte[] {0, 1}; + return Stream.of( + arguments(arguments(INS_REP, 0, 1, 10), input1, new byte[] {10, 0}), + arguments(arguments(INS_REP, 1, 1, 20), input1, new byte[] {0, 20}), + arguments(arguments(INS_REP, 0, 2, 10), input1, new byte[] {10, 10, 0}), + arguments(arguments(INS_REP, 1, 2, 20), input1, new byte[] {0, 20, 20}), + arguments(arguments(INS_REP, 0, 3, 10), input1, new byte[] {10, 10, 10, 0}), + arguments(arguments(INS_REP, 1, 3, 20), input1, new byte[] {0, 20, 20, 20}), + arguments(arguments(INS_REP, 0, 1, 10), input2, new byte[] {10, 0, 1}), + arguments(arguments(INS_REP, 1, 1, 20), input2, new byte[] {0, 20, 1}), + arguments(arguments(INS_REP, 2, 1, 30), input2, new byte[] {0, 1, 30}), + arguments(arguments(INS_REP, 0, 2, 10), input2, new byte[] {10, 10, 0, 1}), + arguments(arguments(INS_REP, 1, 2, 20), input2, new byte[] {0, 20, 20, 1}), + arguments(arguments(INS_REP, 2, 2, 30), input2, new byte[] {0, 1, 30, 30})); + } + @ParameterizedTest @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes"}) void testMutatorOperations(Arguments args, byte[] input, byte[] expected) { From db900794362344c1e51d36713f8d3c6750c73f6b Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Fri, 4 Apr 2025 09:05:11 +0200 Subject: [PATCH 13/28] TMP: test changeByte --- .../LibFuzzerMutatorFactoryTest.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index b5a14261c..5a99c2b0d 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -114,8 +114,27 @@ static Stream insertRepeatedBytes() { arguments(arguments(INS_REP, 2, 2, 30), input2, new byte[] {0, 1, 30, 30})); } + static Stream changeByte() { + final byte[] input1 = new byte[] {0}; + final byte[] input2 = new byte[] {0, 1}; + final byte[] input3 = new byte[] {1, 2, 3}; + return Stream.of( + arguments(arguments(MUT_BYTE, 0, 1), input1, new byte[] {1}), + arguments(arguments(MUT_BYTE, 0, 10), input1, new byte[] {10}), + arguments(arguments(MUT_BYTE, 0, 1), input2, new byte[] {1, 1}), + arguments(arguments(MUT_BYTE, 0, 10), input2, new byte[] {10, 1}), + arguments(arguments(MUT_BYTE, 1, 20), input2, new byte[] {0, 20}), + arguments(arguments(MUT_BYTE, 1, 0), input2, new byte[] {0, 0}), + arguments(arguments(MUT_BYTE, 0, 1), input3, new byte[] {1, 2, 3}), + arguments(arguments(MUT_BYTE, 0, 10), input3, new byte[] {10, 2, 3}), + arguments(arguments(MUT_BYTE, 1, 20), input3, new byte[] {1, 20, 3}), + arguments(arguments(MUT_BYTE, 1, 0), input3, new byte[] {1, 0, 3}), + arguments(arguments(MUT_BYTE, 2, 30), input3, new byte[] {1, 2, 30}), + arguments(arguments(MUT_BYTE, 2, 0), input3, new byte[] {1, 2, 0})); + } + @ParameterizedTest - @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes"}) + @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes", "changeByte"}) void testMutatorOperations(Arguments args, byte[] input, byte[] expected) { Optional> opt = LibFuzzerMutatorFactory.tryCreate( From 1b80de31b1e83166c2431c0a83dab31df2a22ef9 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 5 Apr 2025 11:47:24 +0200 Subject: [PATCH 14/28] TMP: changeBit --- .../libfuzzer/LibFuzzerMutatorFactory.java | 13 +++++++++- .../LibFuzzerMutatorFactoryTest.java | 25 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index ea542794f..0ee4e00d7 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -49,7 +49,8 @@ private interface MutationFunction { LibFuzzerMutator::deleteRandomChunk, LibFuzzerMutator::insertRandomByte, LibFuzzerMutator::insertRepeatedBytes, - LibFuzzerMutator::changeByte + LibFuzzerMutator::changeByte, + LibFuzzerMutator::changeBit, }; @CheckReturnValue @@ -129,6 +130,16 @@ public byte[] mutate(byte[] value, PseudoRandom prng) { return enforceLength(prng.pickIn(MUTATION_FUNCTIONS).mutate(value, maxLengthIncrease, prng)); } + public static byte[] changeBit(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (value.length == 0) { + return new byte[0]; + } + int pos = prng.indexIn(value.length); + byte[] out = Arrays.copyOf(value, value.length); + out[pos] ^= (1 << (byte) prng.closedRange(0, 7)); + return out; + } + public static byte[] changeByte(byte[] value, int maxSizeIncrease, PseudoRandom prng) { if (value.length == 0) { return new byte[0]; diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java index 5a99c2b0d..e55435eb4 100644 --- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java +++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactoryTest.java @@ -52,6 +52,7 @@ void testInit() { static final int INS_BYTE = 1; static final int INS_REP = 2; static final int MUT_BYTE = 3; + static final int MUT_BIT = 4; static Stream deleteChunk() { final byte[] even = new byte[] {0, 1, 2, 3}; @@ -133,8 +134,30 @@ static Stream changeByte() { arguments(arguments(MUT_BYTE, 2, 0), input3, new byte[] {1, 2, 0})); } + static Stream changeBit() { + final byte[] zeroes = new byte[] {0}; + final byte[] ones = new byte[] {(byte) 0xFF}; + return Stream.of( + arguments(arguments(MUT_BIT, 0, 0), zeroes, new byte[] {1}), + arguments(arguments(MUT_BIT, 0, 1), zeroes, new byte[] {2}), + arguments(arguments(MUT_BIT, 0, 2), zeroes, new byte[] {4}), + arguments(arguments(MUT_BIT, 0, 3), zeroes, new byte[] {8}), + arguments(arguments(MUT_BIT, 0, 4), zeroes, new byte[] {16}), + arguments(arguments(MUT_BIT, 0, 5), zeroes, new byte[] {32}), + arguments(arguments(MUT_BIT, 0, 6), zeroes, new byte[] {64}), + arguments(arguments(MUT_BIT, 0, 7), zeroes, new byte[] {(byte) 128}), + arguments(arguments(MUT_BIT, 0, 0), ones, new byte[] {(byte) 0b11111110}), + arguments(arguments(MUT_BIT, 0, 1), ones, new byte[] {(byte) 0b11111101}), + arguments(arguments(MUT_BIT, 0, 2), ones, new byte[] {(byte) 0b11111011}), + arguments(arguments(MUT_BIT, 0, 3), ones, new byte[] {(byte) 0b11110111}), + arguments(arguments(MUT_BIT, 0, 4), ones, new byte[] {(byte) 0b11101111}), + arguments(arguments(MUT_BIT, 0, 5), ones, new byte[] {(byte) 0b11011111}), + arguments(arguments(MUT_BIT, 0, 6), ones, new byte[] {(byte) 0b10111111}), + arguments(arguments(MUT_BIT, 0, 7), ones, new byte[] {(byte) 0b01111111})); + } + @ParameterizedTest - @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes", "changeByte"}) + @MethodSource({"deleteChunk", "insertByte", "insertRepeatedBytes", "changeByte", "changeBit"}) void testMutatorOperations(Arguments args, byte[] input, byte[] expected) { Optional> opt = LibFuzzerMutatorFactory.tryCreate( From 45f86ef7cfa7a0cb11ba824511954d7fc34d56fc Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 5 Apr 2025 11:48:19 +0200 Subject: [PATCH 15/28] TMP: Always use mutator framework when unchained --- .../jazzer/driver/FuzzTargetRunner.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 7785f7d42..3c22b127f 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -119,6 +119,7 @@ public final class FuzzTargetRunner { private static final LifecycleMethodsInvoker lifecycleMethodsInvoker; private static final boolean useFuzzedDataProvider; private static final ArgumentsMutator mutator; + private static final ArgumentsMutator unchainedMutator; private static final ReproducerTemplate reproducerTemplate; private static Consumer fatalFindingHandlerForJUnit; @@ -160,10 +161,13 @@ public final class FuzzTargetRunner { if (useMutatorFramework) { mutator = ArgumentsMutator.forMethodOrThrow(fuzzTarget.method); - Log.info("Using mutator: " + mutator); + unchainedMutator = mutator; } else { mutator = null; + unchainedMutator = ArgumentsMutator.forMethodOrThrow(fuzzTarget.method); } + Log.info("Mutator: " + mutator); + Log.info("Unchained mutator: " + unchainedMutator); if (useHooks) { // libFuzzer will clear the coverage map after this method returns and keeps no record of the @@ -446,11 +450,6 @@ public static int startLibFuzzer(List args) { // TODO when done prototyping: use UnchainedOptions (or similar) to pass the arguments instead of // a Map public static int startUnchainedFuzzer(Map args) { - if (mutator == null) { - Log.error("Mutator framework is required for unchained fuzzing"); - exit(1); - } - // print all args for (Map.Entry entry : args.entrySet()) { System.err.println(entry.getKey() + ": " + entry.getValue()); @@ -503,8 +502,8 @@ public static int startUnchainedFuzzer(Map args) { Throwable finding = null; // select a random input from corpus int index = (int) (Math.random() * corpus.size()); - mutator.read(new ByteArrayInputStream(corpus.get(index))); - mutator.mutate(seed); + unchainedMutator.read(new ByteArrayInputStream(corpus.get(index))); + unchainedMutator.mutate(seed); try { lifecycleMethodsInvoker.beforeEachExecution(); @@ -518,11 +517,11 @@ public static int startUnchainedFuzzer(Map args) { try { Object fuzzTargetInstance = lifecycleMethodsInvoker.getTestClassInstance(); // detaching because we mutate - mutator.invoke(fuzzTargetInstance, true); + unchainedMutator.invoke(fuzzTargetInstance, true); } catch (Throwable uncaughtFinding) { finding = uncaughtFinding; } finally { - mutator.finishFuzzingIteration(); + unchainedMutator.finishFuzzingIteration(); try { lifecycleMethodsInvoker.afterEachExecution(); } catch (Throwable t) { From d1ee5f05e90a1b22025d35ebcaf159080332b24a Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 5 Apr 2025 11:48:44 +0200 Subject: [PATCH 16/28] TMP: don't waste time returning empty byte array --- .../mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index 0ee4e00d7..3255f2537 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -165,7 +165,7 @@ public static byte[] deleteRandomChunk(byte[] value, int maxSizeIncrease, Pseudo public static byte[] insertRandomByte(byte[] value, int maxSizeIncrease, PseudoRandom prng) { if (maxSizeIncrease == 0) { - return value; + return new byte[] {(byte) prng.closedRange(0, 255)}; } int insertPos = prng.indexIn(value.length + 1); byte[] out = new byte[value.length + 1]; From 611f381585caaab859db04d40f4b43f318a4bf22 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 10:05:24 +0200 Subject: [PATCH 17/28] Calls to libFuzzer mutateDefault are now Java-native --- .../lang/FloatingPointMutatorFactory.java | 12 ++++---- .../mutator/lang/IntegralMutatorFactory.java | 20 ++++++------- .../mutation/mutator/libfuzzer/BUILD.bazel | 1 - .../mutator/libfuzzer/LibFuzzerMutate.java | 28 ++++++++----------- .../libfuzzer/LibFuzzerMutatorFactory.java | 6 +++- .../jazzer/runtime/BUILD.bazel | 10 ------- .../jazzer/driver/BUILD.bazel | 9 ------ 7 files changed, 33 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java index 356495d17..ad4c2fc24 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/FloatingPointMutatorFactory.java @@ -162,8 +162,8 @@ private float[] collectSpecialValues(float minValue, float maxValue) { return specialValuesArray; } - public float mutateWithLibFuzzer(float value) { - return LibFuzzerMutate.mutateDefault(value, this, 0); + public float mutateWithLibFuzzer(float value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault(value, this, 0, prng); } @Override @@ -196,7 +196,7 @@ public Float mutate(Float value, PseudoRandom prng) { result = mutateWithMathematicalFn(value, prng); break; case 4: - result = mutateWithLibFuzzer(value); + result = mutateWithLibFuzzer(value, prng); break; case 5: // random in range cannot exceed the given bounds (and cannot be NaN) result = prng.closedRange(minValue, maxValue); @@ -442,8 +442,8 @@ private double[] collectSpecialValues(double minValue, double maxValue) { .toArray(); } - public double mutateWithLibFuzzer(double value) { - return LibFuzzerMutate.mutateDefault(value, this, 0); + public double mutateWithLibFuzzer(double value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault(value, this, 0, prng); } @Override @@ -476,7 +476,7 @@ public Double mutate(Double value, PseudoRandom prng) { result = mutateWithMathematicalFn(value, prng); break; case 4: - result = mutateWithLibFuzzer(value); + result = mutateWithLibFuzzer(value, prng); break; case 5: // random in range cannot exceed the given bounds (and cannot be NaN) result = prng.closedRange(minValue, maxValue); diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java index 5f75cf5e3..e560bd1a1 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/IntegralMutatorFactory.java @@ -50,8 +50,8 @@ public Optional> tryCreate( return Optional.of( new AbstractIntegralMutator(type, Byte.MIN_VALUE, Byte.MAX_VALUE) { @Override - protected long mutateWithLibFuzzer(long value) { - return LibFuzzerMutate.mutateDefault((byte) value, this, 0); + protected long mutateWithLibFuzzer(long value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault((byte) value, this, 0, prng); } @Override @@ -83,8 +83,8 @@ public void write(Byte value, DataOutputStream out) throws IOException { return Optional.of( new AbstractIntegralMutator(type, Short.MIN_VALUE, Short.MAX_VALUE) { @Override - protected long mutateWithLibFuzzer(long value) { - return LibFuzzerMutate.mutateDefault((short) value, this, 0); + protected long mutateWithLibFuzzer(long value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault((short) value, this, 0, prng); } @Override @@ -116,8 +116,8 @@ public void write(Short value, DataOutputStream out) throws IOException { return Optional.of( new AbstractIntegralMutator(type, Integer.MIN_VALUE, Integer.MAX_VALUE) { @Override - protected long mutateWithLibFuzzer(long value) { - return LibFuzzerMutate.mutateDefault((int) value, this, 0); + protected long mutateWithLibFuzzer(long value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault((int) value, this, 0, prng); } @Override @@ -149,8 +149,8 @@ public void write(Integer value, DataOutputStream out) throws IOException { return Optional.of( new AbstractIntegralMutator(type, Long.MIN_VALUE, Long.MAX_VALUE) { @Override - protected long mutateWithLibFuzzer(long value) { - return LibFuzzerMutate.mutateDefault(value, this, 0); + protected long mutateWithLibFuzzer(long value, PseudoRandom prng) { + return LibFuzzerMutate.mutateDefault(value, this, 0, prng); } @Override @@ -290,7 +290,7 @@ protected final long mutateImpl(long value, PseudoRandom prng) { break; case 3: // TODO: Replace this with a structure-aware dictionary/TORC search similar to fuzztest. - value = forceInRange(mutateWithLibFuzzer(value)); + value = forceInRange(mutateWithLibFuzzer(value, prng)); break; } } while (value == previousValue); @@ -328,7 +328,7 @@ private static long mean(long x, long y) { } @ForOverride - protected abstract long mutateWithLibFuzzer(long value); + protected abstract long mutateWithLibFuzzer(long value, PseudoRandom prng); /** * Force value into the closed interval [minValue, maxValue] while preserving as many of its diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel index a4cf1a22e..1a8f36ded 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel @@ -16,7 +16,6 @@ java_library( "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", "//src/main/java/com/code_intelligence/jazzer/mutation/api", "//src/main/java/com/code_intelligence/jazzer/mutation/support", - "//src/main/java/com/code_intelligence/jazzer/runtime:mutator", "@maven//:com_google_errorprone_error_prone_annotations", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java index fc23bbb61..80129c905 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java @@ -18,8 +18,8 @@ import static com.code_intelligence.jazzer.mutation.support.Preconditions.require; +import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.Serializer; -import com.code_intelligence.jazzer.runtime.Mutator; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -32,7 +32,7 @@ public final class LibFuzzerMutate { */ public static final String MOCK_SIZE_KEY = "libfuzzermutator.mock.newsize"; - public static byte[] mutateDefault(byte[] data, int maxSizeIncrease) { + public static byte[] mutateDefault(byte[] data, int maxSizeIncrease, PseudoRandom prng) { require(maxSizeIncrease >= 0); byte[] mutatedBytes; if (maxSizeIncrease == 0) { @@ -40,15 +40,11 @@ public static byte[] mutateDefault(byte[] data, int maxSizeIncrease) { } else { mutatedBytes = Arrays.copyOf(data, data.length + maxSizeIncrease); } - int newSize = defaultMutate(mutatedBytes, data.length); - if (newSize == 0) { - // Mutation failed. This should happen very rarely. - return data; - } - return Arrays.copyOf(mutatedBytes, newSize); + byte [] out = defaultMutate(mutatedBytes, data.length, prng); + return out; } - public static T mutateDefault(T value, Serializer serializer, int maxSizeIncrease) { + public static T mutateDefault(T value, Serializer serializer, int maxSizeIncrease, PseudoRandom prng) { require(maxSizeIncrease >= 0); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { @@ -58,7 +54,7 @@ public static T mutateDefault(T value, Serializer serializer, int maxSize "writeExclusive is not expected to throw if the underlying stream doesn't", e); } - byte[] mutatedBytes = mutateDefault(out.toByteArray(), maxSizeIncrease); + byte[] mutatedBytes = mutateDefault(out.toByteArray(), maxSizeIncrease, prng); try { return serializer.readExclusive(new ByteArrayInputStream(mutatedBytes)); @@ -68,14 +64,14 @@ public static T mutateDefault(T value, Serializer serializer, int maxSize } } - private static int defaultMutate(byte[] buffer, int size) { + private static byte[] defaultMutate(byte[] buffer, int size, PseudoRandom prng) { // TODO: implement byte [] mutator in Java and get rid of all JNI calls in the mutator // framework, including this mock - if (Mutator.SHOULD_MOCK) { - return defaultMutateMock(buffer, size); - } else { - return Mutator.defaultMutateNative(buffer, size); - } +// if (Mutator.SHOULD_MOCK) { +// return defaultMutateMock(buffer, size); +// } else { + return LibFuzzerMutatorFactory.LibFuzzerMutator.mutate(buffer, size, prng); + //} } private static int defaultMutateMock(byte[] buffer, int size) { diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index 3255f2537..516f2e546 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -64,7 +64,7 @@ public static Optional> tryCreate(AnnotatedType type) { } @Immutable - private static final class LibFuzzerMutator extends SerializingMutator { + public static final class LibFuzzerMutator extends SerializingMutator { private final int minLength; private final int maxLength; @@ -130,6 +130,10 @@ public byte[] mutate(byte[] value, PseudoRandom prng) { return enforceLength(prng.pickIn(MUTATION_FUNCTIONS).mutate(value, maxLengthIncrease, prng)); } + public static byte[] mutate(byte[] value, int maxLengthIncrease, PseudoRandom prng) { + return prng.pickIn(MUTATION_FUNCTIONS).mutate(value, maxLengthIncrease, prng); + } + public static byte[] changeBit(byte[] value, int maxSizeIncrease, PseudoRandom prng) { if (value.length == 0) { return new byte[0]; diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 69adaf6b7..7ae18c506 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -139,15 +139,6 @@ java_jni_library( ], ) -java_jni_library( - name = "mutator", - srcs = ["Mutator.java"], - visibility = [ - "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer:__pkg__", - "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__", - ], -) - java_library( name = "runtime", srcs = [ @@ -165,7 +156,6 @@ java_library( ], runtime_deps = [ ":fuzz_target_runner_natives", - ":mutator", # Access to Unsafe is possible without any tricks if the class that does it is loaded by the # bootstrap loader. We thus want Jazzer to use this class from jazzer_bootstrap. "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider", diff --git a/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel index 4f2deef0e..1f648efbf 100644 --- a/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -29,7 +29,6 @@ cc_library( ":fuzz_target_runner", ":jazzer_fuzzer_callbacks", ":libfuzzer_callbacks", - ":mutator", ], ) @@ -126,14 +125,6 @@ cc_library( alwayslink = True, ) -cc_library( - name = "mutator", - srcs = ["mutator.cpp"], - deps = ["//src/main/java/com/code_intelligence/jazzer/runtime:mutator.hdrs"], - # Symbols are only referenced dynamically via JNI. - alwayslink = True, -) - cc_library( name = "init_jazzer_preload", srcs = ["init_jazzer_preload.cpp"], From 430020a037778823bbeddd92b85422283c204457 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 12:03:20 +0200 Subject: [PATCH 18/28] add coverage map to Java land --- .../jazzer/driver/BUILD.bazel | 1 + .../jazzer/driver/FuzzTargetRunner.java | 145 ++++++++++++----- .../jazzer/junit/FuzzTestExecutor.java | 154 ++++++++++-------- .../mutator/libfuzzer/LibFuzzerMutate.java | 15 +- .../jazzer/runtime/BUILD.bazel | 1 + .../jazzer/runtime/CoverageMap.java | 6 + 6 files changed, 206 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 63f1fe96a..96d413061 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -117,6 +117,7 @@ java_jni_library( "//src/main/java/com/code_intelligence/jazzer/instrumentor", "//src/main/java/com/code_intelligence/jazzer/mutation", "//src/main/java/com/code_intelligence/jazzer/runtime:constants", + "//src/main/java/com/code_intelligence/jazzer/runtime:coverage_map", "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap_compile_only", "//src/main/java/com/code_intelligence/jazzer/utils:log", "//src/main/java/com/code_intelligence/jazzer/utils:manifest_utils", diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 3c22b127f..c9f93415f 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -18,6 +18,8 @@ import static com.code_intelligence.jazzer.driver.Constants.JAZZER_FINDING_EXIT_CODE; import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID; +import static com.code_intelligence.jazzer.runtime.CoverageMap.getCoveredIds; +import static com.code_intelligence.jazzer.runtime.CoverageMap.resetCoveredIds; import static java.lang.System.exit; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.joining; @@ -40,6 +42,8 @@ import java.lang.invoke.MethodHandles; import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -343,14 +347,13 @@ private static int runOne(long dataPtr, int dataLength) { ignoredTokens.stream() .map(token -> Long.toUnsignedString(token, 16)) .collect(joining(",")), - Stream.concat( - Opt.autofuzzIgnore.get().stream(), Stream.of(finding.getClass().getName())) + concat(Opt.autofuzzIgnore.get().stream(), Stream.of(finding.getClass().getName())) .collect(joining(",")))); } if (fatalFindingHandlerForJUnit == null) { // When running a legacy fuzzerTestOneInput test, exit now with the correct exit code. // This will trigger the shutdown hook that runs fuzzerTearDown. - System.exit(JAZZER_FINDING_EXIT_CODE); + exit(JAZZER_FINDING_EXIT_CODE); } else { // When running within JUnit, pass control back to FuzzTestExecutor, which has received // the finding via the handler. @@ -487,6 +490,36 @@ public static int startUnchainedFuzzer(Map args) { List corpus = new ArrayList<>(); corpus.add(initialBuf); + List corpusDirs = (List) args.get("corpusDirs"); + for (String dirName : corpusDirs) { + Path dir = Path.of(dirName); + System.err.println("Corpus dirs: " + dir); + // add all files in dir to coprus + try (Stream paths = Files.walk(dir)) { + List files = paths.filter(Files::isRegularFile).collect(toList()); + System.err.println("Found " + files.size() + " files in corpus dir"); + for (Path file : files) { + try { + byte[] fileBytes = Files.readAllBytes(file); + corpus.add(fileBytes); + } catch (IOException e) { + System.err.println("Failed to read file: " + file + " : " + e); + } + } + } catch (IOException e) { + System.err.println("Failed to read corpus dir: " + dir + " : " + e); + } + } + + // if (corpusDirs != null) { + // for (String corpusDir : corpusDirs) { + // System.err.println("Reading corpus from: " + corpusDir); + // //List corpusFromDir = CorpusUtils.readCorpus(corpusDir); + // //System.err.println("Read " + corpusFromDir.size() + " inputs from " + corpusDir); + // //corpus.addAll(corpusFromDir); + // } + // } + Thread fuzzerThread = new Thread( () -> { @@ -495,47 +528,43 @@ public static int startUnchainedFuzzer(Map args) { seed = (long) (Math.random() * Long.MAX_VALUE); System.err.println("Seed: " + seed); } + Set knownIds = new HashSet<>(); - // limited fuzzing loop for now long run = 0; + long printEvery = 1; + + // load corpus and see which features are covered + for (byte[] input : corpus) { + if (run % printEvery == 0) { + System.err.println("#" + run + " corpus: " + corpus.size()); + printEvery *= 2; + } + + unchainedMutator.read(new ByteArrayInputStream(input)); + Set coverageIds = runInput(unchainedMutator, false); + // Add to known ids + int before = knownIds.size(); + knownIds.addAll(coverageIds); + if (knownIds.size() > before) { + System.err.println("New coverage from corpus input! Total: " + knownIds.size()); + } + run++; + } + exit(0); + + // limited fuzzing loop for now + while (run < maxRuns) { - Throwable finding = null; + if (run % printEvery == 0) { + System.err.println("#" + run + " corpus: " + corpus.size()); + printEvery *= 2; + } // select a random input from corpus int index = (int) (Math.random() * corpus.size()); unchainedMutator.read(new ByteArrayInputStream(corpus.get(index))); unchainedMutator.mutate(seed); + Set coverageIds = runInput(unchainedMutator, true); - try { - lifecycleMethodsInvoker.beforeEachExecution(); - } catch (Throwable uncaughtFinding) { - finding = uncaughtFinding; - } - // Do not insert code here. After beforeEachExecution has completed without a - // finding, we should - // always enter the try block that calls afterEachExecution in finally. - if (finding == null) { - try { - Object fuzzTargetInstance = lifecycleMethodsInvoker.getTestClassInstance(); - // detaching because we mutate - unchainedMutator.invoke(fuzzTargetInstance, true); - } catch (Throwable uncaughtFinding) { - finding = uncaughtFinding; - } finally { - unchainedMutator.finishFuzzingIteration(); - try { - lifecycleMethodsInvoker.afterEachExecution(); - } catch (Throwable t) { - if (finding != null) { - // We already have a finding and do not know whether the fuzz target is in - // an expected - // state, so report this as a warning rather than an error or finding. - Log.warn("Failed to run lifecycle method", t); - } else { - finding = t; - } - } - } - } seed++; run++; } @@ -550,6 +579,46 @@ public static int startUnchainedFuzzer(Map args) { return 0; } + public static Set runInput(ArgumentsMutator mutator, boolean detach) { + Throwable finding = null; + Set coveredIds = Set.of(); + try { + lifecycleMethodsInvoker.beforeEachExecution(); + } catch (Throwable uncaughtFinding) { + finding = uncaughtFinding; + } + // Do not insert code here. After beforeEachExecution has completed without a + // finding, we should + // always enter the try block that calls afterEachExecution in finally. + if (finding == null) { + try { + Object fuzzTargetInstance = lifecycleMethodsInvoker.getTestClassInstance(); + // detaching because we mutate + resetCoveredIds(); + unchainedMutator.invoke(fuzzTargetInstance, true); + coveredIds = getCoveredIds(); + } catch (Throwable uncaughtFinding) { + finding = uncaughtFinding; + } finally { + unchainedMutator.finishFuzzingIteration(); + try { + lifecycleMethodsInvoker.afterEachExecution(); + } catch (Throwable t) { + if (finding != null) { + // We already have a finding and do not know whether the fuzz target is in + // an expected + // state, so report this as a warning rather than an error or finding. + Log.warn("Failed to run lifecycle method", t); + } else { + finding = t; + throw new RuntimeException(finding); + } + } + } + } + return coveredIds; + } + public static void registerFatalFindingHandlerForJUnit(Consumer findingHandler) { FuzzTargetRunner.fatalFindingHandlerForJUnit = Objects.requireNonNull(findingHandler); } @@ -568,7 +637,7 @@ private static void shutdown() { lifecycleMethodsInvoker.afterLastExecution(); } catch (Throwable t) { Log.finding(t); - System.exit(JAZZER_FINDING_EXIT_CODE); + exit(JAZZER_FINDING_EXIT_CODE); } } @@ -612,7 +681,7 @@ private static void dumpReproducer(byte[] data) { } catch (IOException e) { Log.error("Failed to create reproducer", e); // Don't let libFuzzer print a native stack trace. - System.exit(1); + exit(1); throw new IllegalStateException("Not reached"); } } else { diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index afe772f9b..93f94d798 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -37,7 +37,6 @@ import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.List; @@ -74,7 +73,7 @@ private FuzzTestExecutor(List libFuzzerArgs, Optional javaSeedsDir private FuzzTestExecutor(Map fuzzerArgs) { this.libFuzzerArgs = null; this.fuzzerArgs = fuzzerArgs; - this.javaSeedsDir = (Optional) fuzzerArgs.get("corpus"); + this.javaSeedsDir = (Optional) fuzzerArgs.get("seedsDir"); this.isUnchained = true; } @@ -124,9 +123,12 @@ public static FuzzTestExecutor prepareUnchained( addInputAndSeedDirs(context, corpusFilesOrDirs, createDefaultGeneratedCorpusDir)); } - fuzzerArgs.put("corpus", javaSeedsDir); + fuzzerArgs.put("seedsDir", javaSeedsDir); + System.err.println("-------------------------- Java seeds dir: " + javaSeedsDir); dictionaryPath.ifPresent(s -> fuzzerArgs.put("dictionary", s)); + System.err.println("-------------------------- Corpus dirs/files: " + corpusFilesOrDirs); + fuzzerArgs.put("corpusDirs", corpusFilesOrDirs); fuzzerArgs.put("maxDuration", durationStringToSeconds(maxDuration)); if (maxRuns > 0) { @@ -147,74 +149,83 @@ public static FuzzTestExecutor prepareUnchained( public static FuzzTestExecutor prepare( ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) throws IOException { - if (!hasBeenPrepared.compareAndSet(false, true)) { - throw new FuzzTestConfigurationError( - "FuzzTestExecutor#prepare can only be called once per test run"); - } - - List allSources = - AnnotationSupport.findRepeatableAnnotations( - context.getRequiredTestMethod(), ArgumentsSource.class); - // Non-empty as it always contains FuzzingArgumentsProvider. - ArgumentsSource lastSource = allSources.get(allSources.size() - 1); - // Ensure that our ArgumentsProviders run last so that we can record all the seeds generated by - // user-provided ones. - if (lastSource.value().getPackage() != FuzzTestExecutor.class.getPackage()) { - throw new FuzzTestConfigurationError( - "@FuzzTest must be the last annotation on a fuzz test," - + " but it came after the (meta-)annotation " - + lastSource); - } - - List originalLibFuzzerArgs = Utils.getLibFuzzerArgs(context); - String argv0 = originalLibFuzzerArgs.isEmpty() ? "fake_argv0" : originalLibFuzzerArgs.remove(0); - - ArrayList libFuzzerArgs = new ArrayList<>(); - libFuzzerArgs.add(argv0); - - // Add passed in corpus directories (and files) at the beginning of the arguments list. - // libFuzzer uses the first directory to store discovered inputs, whereas all others are - // only used to provide additional seeds and aren't written into. - List corpusFilesOrDirs = Utils.getCorpusFilesOrDirs(context); - originalLibFuzzerArgs.removeAll(corpusFilesOrDirs); - libFuzzerArgs.addAll(corpusFilesOrDirs); - - // When reproducing individual inputs, we must not add any corpus directories to the command - // line or libFuzzer will fail with "Not a directory: ...; exiting". - Optional javaSeedsDir; - if (!corpusFilesOrDirs.isEmpty() - && corpusFilesOrDirs.stream().map(Paths::get).allMatch(Files::isRegularFile)) { - javaSeedsDir = Optional.empty(); - } else { - // Only create the default generated corpus directory if it is used as the generated corpus - // directory, i.e., if the user didn't provide any custom corpus directories that come before - // it on the libFuzzer command line. - boolean createDefaultGeneratedCorpusDir = corpusFilesOrDirs.isEmpty(); - javaSeedsDir = - Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, createDefaultGeneratedCorpusDir)); - } - - dictionaryPath.ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); - - libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); - if (maxRuns > 0) { - libFuzzerArgs.add("-runs=" + maxRuns); - } - // Disable libFuzzer's out of memory detection: It is only useful for native library fuzzing, - // which we don't support without our native driver, and leads to false positives where it picks - // up IntelliJ's memory usage. - libFuzzerArgs.add("-rss_limit_mb=0"); - if (Utils.permissivelyParseBoolean( - context.getConfigurationParameter("jazzer.valueprofile").orElse("false"))) { - libFuzzerArgs.add("-use_value_profile=1"); - } - - translateJUnitTimeoutToLibFuzzerFlag(context).ifPresent(libFuzzerArgs::add); - - // Prefer original libFuzzerArgs set via command line by appending them last. - libFuzzerArgs.addAll(originalLibFuzzerArgs); - - return new FuzzTestExecutor(libFuzzerArgs, javaSeedsDir); + throw new IOException("Not implemented"); + // if (!hasBeenPrepared.compareAndSet(false, true)) { + // throw new FuzzTestConfigurationError( + // "FuzzTestExecutor#prepare can only be called once per test run"); + // } + // + // List allSources = + // AnnotationSupport.findRepeatableAnnotations( + // context.getRequiredTestMethod(), ArgumentsSource.class); + // // Non-empty as it always contains FuzzingArgumentsProvider. + // ArgumentsSource lastSource = allSources.get(allSources.size() - 1); + // // Ensure that our ArgumentsProviders run last so that we can record all the seeds + // generated by + // // user-provided ones. + // if (lastSource.value().getPackage() != FuzzTestExecutor.class.getPackage()) { + // throw new FuzzTestConfigurationError( + // "@FuzzTest must be the last annotation on a fuzz test," + // + " but it came after the (meta-)annotation " + // + lastSource); + // } + // + // List originalLibFuzzerArgs = Utils.getLibFuzzerArgs(context); + // String argv0 = originalLibFuzzerArgs.isEmpty() ? "fake_argv0" : + // originalLibFuzzerArgs.remove(0); + // + // ArrayList libFuzzerArgs = new ArrayList<>(); + // libFuzzerArgs.add(argv0); + // + // // Add passed in corpus directories (and files) at the beginning of the arguments list. + // // libFuzzer uses the first directory to store discovered inputs, whereas all others are + // // only used to provide additional seeds and aren't written into. + // List corpusFilesOrDirs = Utils.getCorpusFilesOrDirs(context); + // originalLibFuzzerArgs.removeAll(corpusFilesOrDirs); + // libFuzzerArgs.addAll(corpusFilesOrDirs); + // + // // When reproducing individual inputs, we must not add any corpus directories to the + // command + // // line or libFuzzer will fail with "Not a directory: ...; exiting". + // Optional javaSeedsDir; + // if (!corpusFilesOrDirs.isEmpty() + // && corpusFilesOrDirs.stream().map(Paths::get).allMatch(Files::isRegularFile)) { + // javaSeedsDir = Optional.empty(); + // } else { + // // Only create the default generated corpus directory if it is used as the generated + // corpus + // // directory, i.e., if the user didn't provide any custom corpus directories that come + // before + // // it on the libFuzzer command line. + // boolean createDefaultGeneratedCorpusDir = corpusFilesOrDirs.isEmpty(); + // javaSeedsDir = + // Optional.of(addInputAndSeedDirs(context, libFuzzerArgs, + // createDefaultGeneratedCorpusDir)); + // } + // + // dictionaryPath.ifPresent(s -> libFuzzerArgs.add("-dict=" + s)); + // + // libFuzzerArgs.add("-max_total_time=" + durationStringToSeconds(maxDuration)); + // if (maxRuns > 0) { + // libFuzzerArgs.add("-runs=" + maxRuns); + // } + // // Disable libFuzzer's out of memory detection: It is only useful for native library + // fuzzing, + // // which we don't support without our native driver, and leads to false positives where it + // picks + // // up IntelliJ's memory usage. + // libFuzzerArgs.add("-rss_limit_mb=0"); + // if (Utils.permissivelyParseBoolean( + // context.getConfigurationParameter("jazzer.valueprofile").orElse("false"))) { + // libFuzzerArgs.add("-use_value_profile=1"); + // } + // + // translateJUnitTimeoutToLibFuzzerFlag(context).ifPresent(libFuzzerArgs::add); + // + // // Prefer original libFuzzerArgs set via command line by appending them last. + // libFuzzerArgs.addAll(originalLibFuzzerArgs); + // + // return new FuzzTestExecutor(libFuzzerArgs, javaSeedsDir); } private static Optional translateJUnitTimeoutToLibFuzzerFlag(ExtensionContext context) { @@ -445,6 +456,7 @@ public Optional execute( isUnchained ? FuzzTargetRunner.startUnchainedFuzzer(fuzzerArgs) : FuzzTargetRunner.startLibFuzzer(libFuzzerArgs); + System.err.println("########################## JAVA SEEDS DIR " + javaSeedsDir); javaSeedsDir.ifPresent(FuzzTestExecutor::deleteJavaSeedsDir); Throwable finding = atomicFinding.get(); diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java index 80129c905..416b1a854 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutate.java @@ -40,11 +40,12 @@ public static byte[] mutateDefault(byte[] data, int maxSizeIncrease, PseudoRando } else { mutatedBytes = Arrays.copyOf(data, data.length + maxSizeIncrease); } - byte [] out = defaultMutate(mutatedBytes, data.length, prng); + byte[] out = defaultMutate(mutatedBytes, data.length, prng); return out; } - public static T mutateDefault(T value, Serializer serializer, int maxSizeIncrease, PseudoRandom prng) { + public static T mutateDefault( + T value, Serializer serializer, int maxSizeIncrease, PseudoRandom prng) { require(maxSizeIncrease >= 0); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { @@ -67,11 +68,11 @@ public static T mutateDefault(T value, Serializer serializer, int maxSize private static byte[] defaultMutate(byte[] buffer, int size, PseudoRandom prng) { // TODO: implement byte [] mutator in Java and get rid of all JNI calls in the mutator // framework, including this mock -// if (Mutator.SHOULD_MOCK) { -// return defaultMutateMock(buffer, size); -// } else { - return LibFuzzerMutatorFactory.LibFuzzerMutator.mutate(buffer, size, prng); - //} + // if (Mutator.SHOULD_MOCK) { + // return defaultMutateMock(buffer, size); + // } else { + return LibFuzzerMutatorFactory.LibFuzzerMutator.mutate(buffer, size, prng); + // } } private static int defaultMutateMock(byte[] buffer, int size) { diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 7ae18c506..5aad40c16 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -112,6 +112,7 @@ java_jni_library( }), visibility = [ "//src/jmh/java/com/code_intelligence/jazzer/instrumentor:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/driver:__pkg__", "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__", "//src/test:__subpackages__", ], diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index c4442b403..715cf56fb 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -139,6 +139,12 @@ public static Set getCoveredIds() { return Collections.unmodifiableSet(coveredIds); } + public static void resetCoveredIds() { + for (int id = 0; id < currentNumCounters; id++) { + UNSAFE.putByte(countersAddress + id, (byte) 0); + } + } + public static void replayCoveredIds(Set coveredIds) { for (int id : coveredIds) { UNSAFE.putByte(countersAddress + id, (byte) 1); From 652e261fc2ee25d17e164b80e229ab86627d2a37 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 14:40:56 +0200 Subject: [PATCH 19/28] add to corpus on new coverage --- .../mutation/ArgumentsMutatorFuzzTest.java | 2 +- .../jazzer/driver/BUILD.bazel | 5 +- .../jazzer/driver/FuzzTargetRunner.java | 124 ++++++++++++--- .../jazzer/driver/MurmurHash3.java | 148 ++++++++++++++++++ .../jazzer/runtime/CoverageMap.java | 2 +- 5 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/driver/MurmurHash3.java diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index d95e3f60a..e111df89f 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -66,7 +66,7 @@ public class ArgumentsMutatorFuzzTest { * use FuzzedDataProvider to force the top-level fuzzer to not use the mutation framework, for * easier debugging. */ - @FuzzTest + @FuzzTest(isUnchained = true, maxExecutions = 1000000) void allTests(FuzzedDataProvider data) throws Throwable { int index = data.consumeInt(0, methods.size() - 1); Method method = methods.get(index); diff --git a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel index 96d413061..fc4734723 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel @@ -88,7 +88,10 @@ java_library( java_jni_library( name = "fuzz_target_runner", - srcs = ["FuzzTargetRunner.java"], + srcs = [ + "FuzzTargetRunner.java", + "MurmurHash3.java", + ], # This library is loaded by the classes in the agent runtime package as it needs to be available # in the bootstrap class loader. It is packaged here rather than in jazzer_boostrap.jar since # the bootstrap class loader doesn't support resources. diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index c9f93415f..ae4f159ab 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -17,7 +17,9 @@ package com.code_intelligence.jazzer.driver; import static com.code_intelligence.jazzer.driver.Constants.JAZZER_FINDING_EXIT_CODE; +import static com.code_intelligence.jazzer.driver.MurmurHash3.MurmurHash3_x64_128; import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID; +import static com.code_intelligence.jazzer.runtime.CoverageMap.currentNumCounters; import static com.code_intelligence.jazzer.runtime.CoverageMap.getCoveredIds; import static com.code_intelligence.jazzer.runtime.CoverageMap.resetCoveredIds; import static java.lang.System.exit; @@ -38,12 +40,14 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -490,7 +494,12 @@ public static int startUnchainedFuzzer(Map args) { List corpus = new ArrayList<>(); corpus.add(initialBuf); + // Read corpus from disk List corpusDirs = (List) args.get("corpusDirs"); + // filter out non-existent dirs + corpusDirs = + corpusDirs.stream().filter(dir -> Files.isDirectory(Path.of(dir))).collect(toList()); + for (String dirName : corpusDirs) { Path dir = Path.of(dirName); System.err.println("Corpus dirs: " + dir); @@ -511,15 +520,7 @@ public static int startUnchainedFuzzer(Map args) { } } - // if (corpusDirs != null) { - // for (String corpusDir : corpusDirs) { - // System.err.println("Reading corpus from: " + corpusDir); - // //List corpusFromDir = CorpusUtils.readCorpus(corpusDir); - // //System.err.println("Read " + corpusFromDir.size() + " inputs from " + corpusDir); - // //corpus.addAll(corpusFromDir); - // } - // } - + List finalCorpusDirs = corpusDirs; Thread fuzzerThread = new Thread( () -> { @@ -534,37 +535,108 @@ public static int startUnchainedFuzzer(Map args) { long printEvery = 1; // load corpus and see which features are covered + long initialTime = System.currentTimeMillis(); + + // Update exec speed every N runs + long execTimeForTimeWindow = 0; + long lastExecsPerSecond = 0; + for (byte[] input : corpus) { + unchainedMutator.read(new ByteArrayInputStream(input)); + + // Measure execution time + long startTime = System.currentTimeMillis(); + Set coverageIds = runInput(unchainedMutator, false); + execTimeForTimeWindow += System.currentTimeMillis() - startTime; + + if (run % 1000 == 0) { + lastExecsPerSecond = 1000 * 1000 / (execTimeForTimeWindow + 1); + execTimeForTimeWindow = 0; + } + + knownIds.addAll(coverageIds); + if (run % printEvery == 0) { - System.err.println("#" + run + " corpus: " + corpus.size()); + System.err.println( + "#" + + run + + " cov: " + + knownIds.size() + + " total: " + + currentNumCounters + + " corpus: " + + corpus.size() + + " exec/s: " + + lastExecsPerSecond); printEvery *= 2; } + run++; + } + System.err.println( + "INITED with cov: " + + knownIds.size() + + " total: " + + currentNumCounters + + " corpus: " + + corpus.size() + + " exec/s: " + + lastExecsPerSecond); - unchainedMutator.read(new ByteArrayInputStream(input)); - Set coverageIds = runInput(unchainedMutator, false); - // Add to known ids + while (run < maxRuns) { + // TODO: corpus selection strategy + int index = (int) (Math.random() * corpus.size()); + + unchainedMutator.read(new ByteArrayInputStream(corpus.get(index))); + unchainedMutator.mutate(seed); int before = knownIds.size(); + long startTime = System.currentTimeMillis(); + Set coverageIds = runInput(unchainedMutator, false); + long execTime = System.currentTimeMillis() - startTime; + execTimeForTimeWindow += execTime; knownIds.addAll(coverageIds); + + // add to corpus if new coverage is found if (knownIds.size() > before) { - System.err.println("New coverage from corpus input! Total: " + knownIds.size()); + // new coverage found, add to corpus + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + unchainedMutator.write(baos); + final byte[] mutatedBytes = baos.toByteArray(); + corpus.add(mutatedBytes); + + // also write out to disk to the first corpus dir + if (!finalCorpusDirs.isEmpty()) { + // make a hash of the input (use quick non-cryptographic hash) + String fileName = MurmurHash3_x64_128(mutatedBytes, 0); + Path filePath = Paths.get(finalCorpusDirs.get(0), fileName); + try (OutputStream os = Files.newOutputStream(filePath)) { + os.write(mutatedBytes); + System.err.println("Wrote new corpus entry to " + fileName); + } catch (IOException e) { + System.err.println( + "Failed to write new corpus entry to " + fileName + " : " + e); + } + } } - run++; - } - exit(0); - // limited fuzzing loop for now + if (run % 1000 == 0) { + lastExecsPerSecond = 1000 * 1000 / (execTimeForTimeWindow + 1); + execTimeForTimeWindow = 0; + } - while (run < maxRuns) { if (run % printEvery == 0) { - System.err.println("#" + run + " corpus: " + corpus.size()); + System.err.println( + "#" + + run + + " cov: " + + knownIds.size() + + " total: " + + currentNumCounters + + " corpus: " + + corpus.size() + + " exec/s: " + + lastExecsPerSecond); printEvery *= 2; } - // select a random input from corpus - int index = (int) (Math.random() * corpus.size()); - unchainedMutator.read(new ByteArrayInputStream(corpus.get(index))); - unchainedMutator.mutate(seed); - Set coverageIds = runInput(unchainedMutator, true); - seed++; run++; } diff --git a/src/main/java/com/code_intelligence/jazzer/driver/MurmurHash3.java b/src/main/java/com/code_intelligence/jazzer/driver/MurmurHash3.java new file mode 100644 index 000000000..8584a4257 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/driver/MurmurHash3.java @@ -0,0 +1,148 @@ +/* + * Copyright 2025 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. + */ + +/* + * TODO: add original license of MurmurHash3 + * - test correctness + * - benchmark speed + */ +package com.code_intelligence.jazzer.driver; + +import sun.misc.Unsafe; + +public class MurmurHash3 { + // use UNSAFE for speed + private static final Unsafe UNSAFE; + + static { + try { + java.lang.reflect.Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); + singleoneInstanceField.setAccessible(true); + UNSAFE = (Unsafe) singleoneInstanceField.get(null); + } catch (Exception e) { + throw new RuntimeException("Could not initialize Unsafe", e); + } + } + + private static long ROTL64(long x, int r) { + return (x << r) | (x >>> (64 - r)); + } + + public static String MurmurHash3_x64_128(final byte[] in, int seed) { + final int nblocks = in.length / 16; + + long h1 = seed; + long h2 = seed; + + final long c1 = 0x87c37b91114253d5L; + final long c2 = 0x4cf5ad432745937fL; + + long offset = UNSAFE.arrayBaseOffset(byte[].class); + + for (int i = 0; i < nblocks; i++) { + long k1 = UNSAFE.getLong(in, offset + i * 16); + long k2 = UNSAFE.getLong(in, offset + i * 16 + 8); + + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + + h1 = ROTL64(h1, 27); + h1 += h2; + h1 = h1 * 5 + 0x52dce729; + + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + h2 = ROTL64(h2, 31); + h2 += h1; + h2 = h2 * 5 + 0x38495ab5; + } + + int at = nblocks * 16; + int len = in.length; + + long k1 = 0; + long k2 = 0; + + switch (len & 15) { + case 15: + k2 ^= ((long) in[at + 14]) << 48; + case 14: + k2 ^= ((long) in[at + 13]) << 40; + case 13: + k2 ^= ((long) in[at + 12]) << 32; + case 12: + k2 ^= ((long) in[at + 11]) << 24; + case 11: + k2 ^= ((long) in[at + 10]) << 16; + case 10: + k2 ^= ((long) in[at + 9]) << 8; + case 9: + k2 ^= in[at + 8]; + k2 *= c2; + k2 = ROTL64(k2, 33); + k2 *= c1; + h2 ^= k2; + + case 8: + k1 ^= ((long) in[at + 7]) << 56; + case 7: + k1 ^= ((long) in[at + 6]) << 48; + case 6: + k1 ^= ((long) in[at + 5]) << 40; + case 5: + k1 ^= ((long) in[at + 4]) << 32; + case 4: + k1 ^= ((long) in[at + 3]) << 24; + case 3: + k1 ^= ((long) in[at + 2]) << 16; + case 2: + k1 ^= ((long) in[at + 1]) << 8; + case 1: + k1 ^= in[at + 0]; + k1 *= c1; + k1 = ROTL64(k1, 31); + k1 *= c2; + h1 ^= k1; + } + + h1 ^= len; + h2 ^= len; + + h1 += h2; + h2 += h1; + + h1 = fmix64(h1); + h2 = fmix64(h2); + + h1 += h2; + h2 += h1; + return String.format("%016x%016x", h1, h2); + } + + private static long fmix64(long k) { + k ^= k >> 33; + k *= 0xff51afd7ed558ccdL; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53L; + k ^= k >> 33; + return k; + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java index 715cf56fb..8c719de8e 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/CoverageMap.java @@ -90,7 +90,7 @@ public final class CoverageMap { * dynamically as classes are instrumented and should be kept as low as possible as libFuzzer has * to iterate over the whole map for every execution. */ - private static int currentNumCounters = INITIAL_NUM_COUNTERS; + public static int currentNumCounters = INITIAL_NUM_COUNTERS; // Called via reflection. @SuppressWarnings("unused") From 9896590e726c878bf5c150a4a150d7ff6ca761fb Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 15:00:11 +0200 Subject: [PATCH 20/28] sort corpus inputs by size; add only those producing coverage --- .../jazzer/driver/FuzzTargetRunner.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index ae4f159ab..69c610798 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -53,6 +53,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -491,8 +492,8 @@ public static int startUnchainedFuzzer(Map args) { initialBuf[0] = '\n'; // corpus in RAM only for now - List corpus = new ArrayList<>(); - corpus.add(initialBuf); + List initialCorpus = new ArrayList<>(); + initialCorpus.add(initialBuf); // Read corpus from disk List corpusDirs = (List) args.get("corpusDirs"); @@ -510,7 +511,7 @@ public static int startUnchainedFuzzer(Map args) { for (Path file : files) { try { byte[] fileBytes = Files.readAllBytes(file); - corpus.add(fileBytes); + initialCorpus.add(fileBytes); } catch (IOException e) { System.err.println("Failed to read file: " + file + " : " + e); } @@ -520,7 +521,9 @@ public static int startUnchainedFuzzer(Map args) { } } + // Used in the lambda below, has to be effectively final List finalCorpusDirs = corpusDirs; + Thread fuzzerThread = new Thread( () -> { @@ -541,7 +544,20 @@ public static int startUnchainedFuzzer(Map args) { long execTimeForTimeWindow = 0; long lastExecsPerSecond = 0; - for (byte[] input : corpus) { + // We only keep inputs that increase coverage + List corpus = new ArrayList<>(); + + /* + * Run through the corpus once to initialize the known features. + * Here we sort the corpus by input length to get a better + * initial coverage faster. + * TODO: allow user to configure initial corpus and the order inputs are + * executed in. + */ + for (byte[] input : + initialCorpus.stream() + .sorted(Comparator.comparingInt(a -> a.length)) + .collect(toList())) { unchainedMutator.read(new ByteArrayInputStream(input)); // Measure execution time @@ -554,7 +570,11 @@ public static int startUnchainedFuzzer(Map args) { execTimeForTimeWindow = 0; } + long coverageBefore = knownIds.size(); knownIds.addAll(coverageIds); + if (knownIds.size() > coverageBefore) { + corpus.add(input); + } if (run % printEvery == 0) { System.err.println( From bc2b78d6b26b64fc6e4919d3080b5c25dbfb7e3e Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 15:09:27 +0200 Subject: [PATCH 21/28] update TODOs --- .../jazzer/driver/FuzzTargetRunner.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 69c610798..b257cc8f4 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -462,24 +462,18 @@ public static int startUnchainedFuzzer(Map args) { for (Map.Entry entry : args.entrySet()) { System.err.println(entry.getKey() + ": " + entry.getValue()); } - // Native (Java) corpus management ### LATER - // read corpus and deserialize using mutator - // get features from each input - // - coverage features - // - execution time - // - memory usage - // apply user-configurable selection fn (SEL) to keep inputs based on the features - // this fn is also used during fuzzing - - // Mutation Core ## LATER - - // Native (Java) fuzzing loop in a thread ### NOW - // - select input from corpus using user-configurable fuzzing strategy fn (FUZ) - // - apply mutator to generate new input - // - run fuzz target with new input - // - collect features and apply SEL to keep or discard input - // - if input is kept, write out the corpus - // Also start timeout thread that kills the fuzzing thread after timeout + /* TODO + * - TORCs in LibfuzzerMutator + * - entropic scheduler + * - allow running all fuzz tests + * - start a timeout thread that kills the fuzzing thread after timeout + * - track memory usage + * TODO PLUGINs + * - user callback for initial corpus run + * - user callback for input scheduling + * - user callback for feature extraction/computation (e.g. users want to focus on longer-running inputs) + * + */ // Extract some args. // TODO: use UnchainedOptions instead From 210ff7eb708d6b71232b8fa44608540978dd8072 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 22:15:45 +0200 Subject: [PATCH 22/28] start a timeout thread (still need to hard kill on invoke timeout) --- .../jazzer/driver/FuzzTargetRunner.java | 32 ++++++++++++++++++- .../jazzer/junit/FuzzTest.java | 4 +-- .../jazzer/junit/FuzzTestExecutor.java | 4 +-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index b257cc8f4..58dc830eb 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -479,6 +479,8 @@ public static int startUnchainedFuzzer(Map args) { // TODO: use UnchainedOptions instead Long maxRuns = (Long) args.get("maxRuns"); Long maxDuration = (Long) args.get("maxDuration"); + System.err.println("maxRuns: " + maxRuns); + System.err.println("maxDuration: " + maxDuration); // if seed is null, use a random seed and print it // make byte arrey of size 1 containing "\n" @@ -518,6 +520,9 @@ public static int startUnchainedFuzzer(Map args) { // Used in the lambda below, has to be effectively final List finalCorpusDirs = corpusDirs; + // should stop is used to signal the fuzzer thread to stop + boolean shouldStop = false; + Thread fuzzerThread = new Thread( () -> { @@ -596,7 +601,11 @@ public static int startUnchainedFuzzer(Map args) { + " exec/s: " + lastExecsPerSecond); - while (run < maxRuns) { + while (maxRuns <= 0 || run < maxRuns) { + if (Thread.interrupted()) { + System.err.println("Fuzzer thread stopping"); + break; + } // TODO: corpus selection strategy int index = (int) (Math.random() * corpus.size()); @@ -656,9 +665,30 @@ public static int startUnchainedFuzzer(Map args) { } }); fuzzerThread.start(); + + // Start the duration thread that kills the fuzzer thread after maxDuration seconds. + Thread durationThread = null; + if (maxDuration > 0) { + durationThread = + new Thread( + () -> { + try { + Thread.sleep(maxDuration * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.err.println("Max duration reached, stopping"); + fuzzerThread.interrupt(); + }); + durationThread.setDaemon(true); + durationThread.start(); + } + try { fuzzerThread.join(); } catch (InterruptedException e) { + // stop duration thread + if (durationThread != null) durationThread.interrupt(); e.printStackTrace(); } diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java index 814820184..41358c410 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTest.java @@ -114,7 +114,7 @@ * *

This option has no effect during regression testing. */ - String maxDuration() default "5m"; + String maxDuration() default "10s"; /** * If set to a positive number, the fuzz test function will be executed at most this many times @@ -127,7 +127,7 @@ */ long maxExecutions() default 0; - boolean isUnchained() default false; + boolean isUnchained() default true; /** * Controls the JUnit lifecycle of fuzz tests during fuzzing. diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index 93f94d798..acb3691d8 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -131,9 +131,7 @@ public static FuzzTestExecutor prepareUnchained( fuzzerArgs.put("corpusDirs", corpusFilesOrDirs); fuzzerArgs.put("maxDuration", durationStringToSeconds(maxDuration)); - if (maxRuns > 0) { - fuzzerArgs.put("maxRuns", maxRuns); - } + fuzzerArgs.put("maxRuns", maxRuns); if (Utils.permissivelyParseBoolean( context.getConfigurationParameter("jazzer.valueprofile").orElse("false"))) { From d9cec6417cd71ed358113f687ce2020b6293c01f Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 22:16:20 +0200 Subject: [PATCH 23/28] FUZZING FIXME LATER: looking at comparison dataflow --- .../runtime/TraceDataFlowNativeCallbacks.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 708db267c..105e1acc8 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -16,7 +16,6 @@ package com.code_intelligence.jazzer.runtime; -import com.github.fmeum.rules_jni.RulesJni; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; @@ -33,10 +32,6 @@ public final class TraceDataFlowNativeCallbacks { // UTF-8. private static final Charset FUZZED_DATA_CHARSET = Charset.forName("CESU8"); - static { - RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver"); - } - // It is possible for RulesJni#loadLibrary to trigger a hook even though it isn't instrumented if // it uses regexes, which it does with at least some JDKs due to its use of String#format. This // led to exceptions in the past when the hook ended up calling traceStrcmp or traceStrstr before @@ -48,16 +43,23 @@ public final class TraceDataFlowNativeCallbacks { // completed yet. private static final boolean NATIVE_INITIALIZED = true; - public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + // public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); + public static void traceMemcmp(byte[] b1, byte[] b2, int result, int pc) { + // System.out.println("traceMemcmp called with: "+ Arrays.toString(b1)+" , + // "+Arrays.toString(b2)+" , "+result+" , "+pc); + + } public static void traceStrcmp(String s1, String s2, int result, int pc) { if (NATIVE_INITIALIZED) { + // System.out.println("traceStrcmp called with: "+ s1+" , "+s2+" , "+result+" , "+pc); traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc); } } public static void traceStrstr(String s1, String s2, int pc) { if (NATIVE_INITIALIZED) { + // System.out.println("---traceStrstr called with: "+ s1+" , "+s2+" , "+pc); traceStrstr0(encodeForLibFuzzer(s2), pc); } } @@ -103,7 +105,13 @@ public static void traceGenericCmp(Object arg1, Object arg2, int pc) { } /* trace-cmp */ - public static native void traceCmpInt(int arg1, int arg2, int pc); + // public static native void traceCmpInt(int arg1, int arg2, int pc); + public static void traceCmpInt(int arg1, int arg2, int pc) { + // System.out.println("####### traceCmpInt called with: "+ arg1+" , "+arg2+" , "+pc); + // // Trace the stack trace + // System.out.println(Arrays.toString(Thread.currentThread().getStackTrace())); + + } public static native void traceConstCmpInt(int arg1, int arg2, int pc); @@ -117,7 +125,10 @@ public static void traceGenericCmp(Object arg1, Object arg2, int pc) { public static native void traceDivLong(long val, int pc); /* trace-gep */ - public static native void traceGep(long val, int pc); + // public static native void traceGep(long val, int pc); + public static void traceGep(long val, int pc) { + System.out.println("####### traceGep called with: " + val + " , " + pc); + } /* indirect-calls */ public static native void tracePcIndir(int callee, int caller); From 01eddb754fc5778df1d258c0e984db7005d2065f Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 27 Sep 2025 22:17:13 +0200 Subject: [PATCH 24/28] allow build system to run all fuzz tests --- .../code_intelligence/jazzer/driver/OptItem.java | 15 ++++++--------- .../jazzer/junit/AgentConfigurator.java | 4 ---- .../jazzer/junit/FuzzTestExecutor.java | 8 -------- .../jazzer/junit/FuzzTestExtensions.java | 16 ++++------------ 4 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java b/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java index 539a4821f..aebceb320 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java @@ -87,9 +87,6 @@ static void registerCommandLineArgs(List> cliArgs) { */ static void registerConfigurationParameters( Function> configurationParameterGetter) { - if (OptItem.configurationParameterGetter.isPresent()) { - throw new IllegalStateException("Configuration parameters have already been set"); - } OptItem.configurationParameterGetter = Optional.of(configurationParameterGetter); } @@ -131,12 +128,12 @@ public final T get() throws IllegalOptionValueException { * {@code newValue}. */ public final boolean setIfDefault(T newValue) throws IllegalOptionValueException { - if (value != null) { - throw new IllegalStateException( - String.format( - "Attempted to set of option %s to %s, but it has already been read elsewhere", - propertyName(), newValue)); - } +// if (value != null) { +// throw new IllegalStateException( +// String.format( +// "Attempted to set of option %s to %s, but it has already been read elsewhere", +// propertyName(), newValue)); +// } Optional explicitValue = getExplicitValue(); if (explicitValue.isPresent()) { value = explicitValue.get(); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfigurator.java b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfigurator.java index fe758141a..7d60f44a7 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/AgentConfigurator.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/AgentConfigurator.java @@ -46,10 +46,6 @@ static void forRegressionTest(ExtensionContext extensionContext) { } static void forFuzzing(ExtensionContext extensionContext) { - if (!hasBeenConfigured.compareAndSet(false, true)) { - throw new IllegalStateException("Only a single fuzz test should be executed per fuzzing run"); - } - applyCommonConfiguration(extensionContext); Opt.instrument.setIfDefault(determineInstrumentationFilters(extensionContext)); diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java index acb3691d8..bbf6ca5b0 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExecutor.java @@ -80,11 +80,6 @@ private FuzzTestExecutor(Map fuzzerArgs) { public static FuzzTestExecutor prepareUnchained( ExtensionContext context, String maxDuration, long maxRuns, Optional dictionaryPath) throws IOException { - if (!hasBeenPrepared.compareAndSet(false, true)) { - throw new FuzzTestConfigurationError( - "FuzzTestExecutor#prepareUnchained can only be called once per test run"); - } - List allSources = AnnotationSupport.findRepeatableAnnotations( context.getRequiredTestMethod(), ArgumentsSource.class); @@ -378,9 +373,6 @@ static void configureAndInstallAgent( Optional dictionaryPath, boolean isUnchained) throws IOException { - if (!agentInstalled.compareAndSet(false, true)) { - return; - } if (Utils.isFuzzing(extensionContext)) { FuzzTestExecutor executor = isUnchained diff --git a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java index 69ba22152..701501eb8 100644 --- a/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java +++ b/src/main/java/com/code_intelligence/jazzer/junit/FuzzTestExtensions.java @@ -183,18 +183,10 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext ext } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException ignore) { } - // Only fuzz the first @FuzzTest that makes it here. - if (FuzzTestExtensions.fuzzTestMethod.compareAndSet( - null, extensionContext.getRequiredTestMethod()) - || extensionContext - .getRequiredTestMethod() - .equals(FuzzTestExtensions.fuzzTestMethod.get())) { - return ConditionEvaluationResult.enabled( - "Fuzzing " + extensionContext.getRequiredTestMethod()); - } - return ConditionEvaluationResult.disabled( - "Only one fuzz test can be run at a time, but multiple tests have been annotated with" - + " @FuzzTest"); + System.err.println( + "******************** extension context" + extensionContext.getRequiredTestMethod()); + FuzzTestExtensions.fuzzTestMethod.set(extensionContext.getRequiredTestMethod()); + return ConditionEvaluationResult.enabled("Fuzzing " + extensionContext.getRequiredTestMethod()); } private static SeedSerializer getOrCreateSeedSerializer(ExtensionContext extensionContext) { From 1d665fa23e762fa20db1eb6aa34a57f3265cd181 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 4 Oct 2025 01:58:45 +0200 Subject: [PATCH 25/28] format --- .../com/code_intelligence/jazzer/driver/OptItem.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java b/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java index aebceb320..1133d8282 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/OptItem.java @@ -128,12 +128,12 @@ public final T get() throws IllegalOptionValueException { * {@code newValue}. */ public final boolean setIfDefault(T newValue) throws IllegalOptionValueException { -// if (value != null) { -// throw new IllegalStateException( -// String.format( -// "Attempted to set of option %s to %s, but it has already been read elsewhere", -// propertyName(), newValue)); -// } + // if (value != null) { + // throw new IllegalStateException( + // String.format( + // "Attempted to set of option %s to %s, but it has already been read elsewhere", + // propertyName(), newValue)); + // } Optional explicitValue = getExplicitValue(); if (explicitValue.isPresent()) { value = explicitValue.get(); From 8165484838c65f067376aad2d2132fc7e4adcc0d Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 4 Oct 2025 01:59:05 +0200 Subject: [PATCH 26/28] add torc --- .../jazzer/driver/FuzzTargetRunner.java | 21 +++++----- .../jazzer/instrumentor/BUILD.bazel | 1 + .../mutation/mutator/libfuzzer/BUILD.bazel | 1 + .../libfuzzer/LibFuzzerMutatorFactory.java | 19 +++++++++ .../jazzer/mutation/mutator/torc/BUILD.bazel | 15 +++++++ .../jazzer/mutation/mutator/torc/Torc.java | 42 +++++++++++++++++++ .../jazzer/runtime/BUILD.bazel | 5 ++- .../runtime/TraceDataFlowNativeCallbacks.java | 7 +++- 8 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/BUILD.bazel create mode 100644 src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 58dc830eb..2fe5f71c7 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -500,22 +500,21 @@ public static int startUnchainedFuzzer(Map args) { for (String dirName : corpusDirs) { Path dir = Path.of(dirName); System.err.println("Corpus dirs: " + dir); - // add all files in dir to coprus - try (Stream paths = Files.walk(dir)) { - List files = paths.filter(Files::isRegularFile).collect(toList()); - System.err.println("Found " + files.size() + " files in corpus dir"); - for (Path file : files) { - try { - byte[] fileBytes = Files.readAllBytes(file); - initialCorpus.add(fileBytes); - } catch (IOException e) { - System.err.println("Failed to read file: " + file + " : " + e); + // Get the number of files in the corpus dir + if (!Files.isDirectory(dir)) { + throw new IllegalArgumentException("Corpus dir is not a directory: " + dir); + } + try { + for (Path f : Files.newDirectoryStream(dir)) { + if (Files.isRegularFile(f)) { + initialCorpus.add(Files.readAllBytes(f)); } } } catch (IOException e) { - System.err.println("Failed to read corpus dir: " + dir + " : " + e); + throw new RuntimeException("Failed to read corpus dir: " + dir, e); } } + System.err.println("Initial corpus size: " + initialCorpus.size()); // Used in the lambda below, has to be effectively final List finalCorpusDirs = corpusDirs; diff --git a/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel index 13015ed77..7b7cf8ed0 100644 --- a/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/instrumentor/BUILD.bazel @@ -25,6 +25,7 @@ kt_jvm_library( ], deps = [ "//src/main/java/com/code_intelligence/jazzer/api:hooks", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc", "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap_compile_only", "//src/main/java/com/code_intelligence/jazzer/utils", "//src/main/java/com/code_intelligence/jazzer/utils:class_name_globber", diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel index 1a8f36ded..6b45ad778 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel @@ -15,6 +15,7 @@ java_library( deps = [ "//src/main/java/com/code_intelligence/jazzer/mutation/annotation", "//src/main/java/com/code_intelligence/jazzer/mutation/api", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc", "//src/main/java/com/code_intelligence/jazzer/mutation/support", "@maven//:com_google_errorprone_error_prone_annotations", ], diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index 516f2e546..8c72e861c 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -23,6 +23,7 @@ import com.code_intelligence.jazzer.mutation.api.Debuggable; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; +import com.code_intelligence.jazzer.mutation.mutator.torc.Torc; import com.code_intelligence.jazzer.mutation.support.RandomSupport; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; @@ -51,6 +52,7 @@ private interface MutationFunction { LibFuzzerMutator::insertRepeatedBytes, LibFuzzerMutator::changeByte, LibFuzzerMutator::changeBit, + LibFuzzerMutator::insertTorcBytes }; @CheckReturnValue @@ -195,6 +197,23 @@ public static byte[] insertRepeatedBytes(byte[] value, int maxSizeIncrease, Pseu return out; } + public static byte[] insertTorcBytes(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + if (maxSizeIncrease == 0) { + return value; + } + byte[] torcBytes = Torc.get(prng); + if (torcBytes == null || torcBytes.length == 0) { + return value; + } + int insertPos = prng.indexIn(value.length + 1); + int insertSize = Math.min(torcBytes.length, maxSizeIncrease); + byte[] out = new byte[value.length + insertSize]; + System.arraycopy(value, 0, out, 0, insertPos); + System.arraycopy(torcBytes, 0, out, insertPos, insertSize); + System.arraycopy(value, insertPos, out, insertPos + insertSize, value.length - insertPos); + return out; + } + private byte[] enforceLength(byte[] mutated) { // if the mutated array libfuzzer returns is too long or short, we truncate or extend it // respectively. if we extend it, then copyOf will fill leftover bytes with 0 diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/BUILD.bazel new file mode 100644 index 000000000..407c429a5 --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/BUILD.bazel @@ -0,0 +1,15 @@ +java_library( + name = "torc", + srcs = [ + "Torc.java", + ], + visibility = [ + "//src/main/java/com/code_intelligence/jazzer/instrumentor:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/mutation:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer:__pkg__", + "//src/main/java/com/code_intelligence/jazzer/runtime:__pkg__", + ], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/api", + ], +) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java new file mode 100644 index 000000000..a8b253a7c --- /dev/null +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java @@ -0,0 +1,42 @@ +/* + * Copyright 2025 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.mutation.mutator.torc; + +import com.code_intelligence.jazzer.mutation.api.PseudoRandom; + +/** + * Table of recent comparisons (Torc) mutator using the Torc library. Uses ring buffer to store + * recent comparisons. + */ +public class Torc { + // ring buffer + private static final int SIZE = 1024; + private static final byte[][] buffer = new byte[SIZE][]; + private static int index = 0; + + private Torc() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static void add(byte[] data) { + buffer[index++ % SIZE] = data; + } + + public static byte[] get(PseudoRandom prng) { + return prng.pickIn(buffer); + } +} diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel index 5aad40c16..675ac8d29 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel @@ -128,7 +128,10 @@ java_jni_library( visibility = [ "//src/main/native/com/code_intelligence/jazzer/driver:__pkg__", ], - deps = ["@maven//:org_ow2_asm_asm"], + deps = [ + "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc", + "@maven//:org_ow2_asm_asm", + ], ) java_jni_library( diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index 105e1acc8..c5f5b2756 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -16,6 +16,7 @@ package com.code_intelligence.jazzer.runtime; +import com.code_intelligence.jazzer.mutation.mutator.torc.Torc; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Method; @@ -45,8 +46,10 @@ public final class TraceDataFlowNativeCallbacks { // public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); public static void traceMemcmp(byte[] b1, byte[] b2, int result, int pc) { - // System.out.println("traceMemcmp called with: "+ Arrays.toString(b1)+" , - // "+Arrays.toString(b2)+" , "+result+" , "+pc); + Torc.add(b1); + Torc.add(b2); + // System.out.println("traceMemcmp called with: "+ Arrays.toString(b1)+ "," + // +Arrays.toString(b2)+" , "+result+" , "+pc); } From f06afcb476e24e4bdf06290fa37f46048983b929 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Sat, 4 Oct 2025 01:59:57 +0200 Subject: [PATCH 27/28] TMP: temporary test long strings --- .../selffuzz/mutation/ArgumentsMutatorFuzzTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index e111df89f..7c2aa3804 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -66,7 +66,7 @@ public class ArgumentsMutatorFuzzTest { * use FuzzedDataProvider to force the top-level fuzzer to not use the mutation framework, for * easier debugging. */ - @FuzzTest(isUnchained = true, maxExecutions = 1000000) + @FuzzTest(isUnchained = true, maxExecutions = 10000000, maxDuration = "10m") void allTests(FuzzedDataProvider data) throws Throwable { int index = data.consumeInt(0, methods.size() - 1); Method method = methods.get(index); @@ -95,6 +95,16 @@ void allTests(FuzzedDataProvider data) throws Throwable { } } + @Solo + @SelfFuzzTest + void fuzzStrings_torc(@NotNull String s0) { + if (s0.equals( + "qbfdbas891rbujebfrldsafbhd81irbewhavfsgdalvfdlasuvfzu3vrvesadolufdvsalfv" + + " dsagolufvdgsalfvffdshakfvdsakfvdksavfgdksafdasgfzdasgfzdasgfdsafdsafdasvfdavsfdasfdasvfdsafdsafdsafdsafdsafdsafdasfdasfdsafdsafdsa")) { + throw new RuntimeException("Found it!"); + } + } + @SelfFuzzTest void fuzzStrings( @NotNull @WithUtf8Length(min = 5, max = 7) String s0, From 5434eb563170bedfe1db0b738fa71324231575d5 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Wed, 19 Nov 2025 09:50:21 +0100 Subject: [PATCH 28/28] TMP: working on TORC --- .../src/test/java/com/example/BUILD.bazel | 3 +- .../java/com/example/MutatorFuzzTest.java | 34 ++++----- .../mutation/ArgumentsMutatorFuzzTest.java | 30 ++++---- .../jazzer/driver/FuzzTargetRunner.java | 14 ++-- .../mutator/lang/StringMutatorFactory.java | 3 +- .../mutation/mutator/libfuzzer/BUILD.bazel | 1 + .../libfuzzer/LibFuzzerMutatorFactory.java | 72 +++++++++++++++---- .../jazzer/mutation/mutator/torc/Torc.java | 40 +++++++++-- .../runtime/TraceDataFlowNativeCallbacks.java | 20 ++---- 9 files changed, 145 insertions(+), 72 deletions(-) diff --git a/examples/junit/src/test/java/com/example/BUILD.bazel b/examples/junit/src/test/java/com/example/BUILD.bazel index e1fc1d5e9..5d4fea808 100644 --- a/examples/junit/src/test/java/com/example/BUILD.bazel +++ b/examples/junit/src/test/java/com/example/BUILD.bazel @@ -227,7 +227,7 @@ java_fuzz_target_test( java_fuzz_target_test( name = "MutatorFuzzTest", srcs = ["MutatorFuzzTest.java"], - allowed_findings = ["java.lang.AssertionError"], + # allowed_findings = ["java.lang.AssertionError"], data = [ "//examples/junit/src/test/resources:MutatorFuzzTestInputs", ], @@ -235,6 +235,7 @@ java_fuzz_target_test( "JAZZER_FUZZ": "1", }, fuzzer_args = [ + "-runs=100", "$(rlocationpaths //examples/junit/src/test/resources:MutatorFuzzTestInputs)", ], target_class = "com.example.MutatorFuzzTest", diff --git a/examples/junit/src/test/java/com/example/MutatorFuzzTest.java b/examples/junit/src/test/java/com/example/MutatorFuzzTest.java index f0f746c94..d230e9360 100644 --- a/examples/junit/src/test/java/com/example/MutatorFuzzTest.java +++ b/examples/junit/src/test/java/com/example/MutatorFuzzTest.java @@ -16,29 +16,29 @@ package com.example; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.code_intelligence.jazzer.driver.FuzzTargetRunner; import com.code_intelligence.jazzer.junit.FuzzTest; import com.code_intelligence.jazzer.mutation.annotation.NotNull; -import java.util.List; -import org.junit.jupiter.api.AfterAll; class MutatorFuzzTest { - @FuzzTest - void mutatorFuzz(List<@NotNull String> list) { + @FuzzTest(maxExecutions = 10000) + void mutatorFuzz(@NotNull String s) { + if (s.length() > 0) { + byte sAt0 = (byte) s.charAt(0); + if (sAt0 == (byte) 0xFF) { + throw new AssertionError("Found expected JUnit mutator test issue"); + } + } + +/* for (int i = 0; i < s.length(); i++) { + + + } */ + /* System.err.println("Fuzzing with input: " + s.length() + ", str: " + s); // Check that the mutator is actually doing something. - if (list != null && list.size() > 3 && list.get(2).equals("mutator")) { + if (s.equals("Hello, world!")) { + System.err.println("HERE-----------------------------------------------------"); throw new AssertionError("Found expected JUnit mutator test issue"); } - } - - @AfterAll - static void assertFuzzTargetRunner() { - // FuzzTargetRunner values are not set in JUnit engine tests. - String jazzerFuzz = System.getenv("JAZZER_FUZZ"); - if (jazzerFuzz != null && !jazzerFuzz.isEmpty()) { - assertEquals(FuzzTargetRunner.mutatorDebugString(), "Arguments[Nullable>]"); - } + } */ } } diff --git a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java index 7c2aa3804..580cded33 100644 --- a/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java +++ b/selffuzz/src/test/java/com/code_intelligence/selffuzz/mutation/ArgumentsMutatorFuzzTest.java @@ -28,9 +28,6 @@ import com.code_intelligence.selffuzz.jazzer.mutation.annotation.WithLength; import com.code_intelligence.selffuzz.jazzer.mutation.mutator.Mutators; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.invoke.MethodHandles; @@ -66,7 +63,7 @@ public class ArgumentsMutatorFuzzTest { * use FuzzedDataProvider to force the top-level fuzzer to not use the mutation framework, for * easier debugging. */ - @FuzzTest(isUnchained = true, maxExecutions = 10000000, maxDuration = "10m") + @FuzzTest(isUnchained = true, maxExecutions = 100000, maxDuration = "10s") void allTests(FuzzedDataProvider data) throws Throwable { int index = data.consumeInt(0, methods.size() - 1); Method method = methods.get(index); @@ -76,20 +73,24 @@ void allTests(FuzzedDataProvider data) throws Throwable { byte[] input = data.consumeRemainingAsBytes(); try { - mutator.init(seed); - ByteArrayOutputStream initedOut = new ByteArrayOutputStream(); - mutator.write(new DataOutputStream(initedOut)); - InputStream inited = new ByteArrayInputStream(initedOut.toByteArray()); + // mutator.init(seed); + // ByteArrayOutputStream initedOut = new ByteArrayOutputStream(); + // mutator.write(new DataOutputStream(initedOut)); + // InputStream inited = new ByteArrayInputStream(initedOut.toByteArray()); mutator.read(new ByteArrayInputStream(input)); mutator.invoke(this, true); + System.err.println("MUTATE"); mutator.mutate(seed); - ByteArrayOutputStream mutatedOut = new ByteArrayOutputStream(); - mutator.write(new DataOutputStream(mutatedOut)); - InputStream mutated = new ByteArrayInputStream(mutatedOut.toByteArray()); + System.err.println("INVOKE**************"); + mutator.invoke(this, true); + + // ByteArrayOutputStream mutatedOut = new ByteArrayOutputStream(); + // mutator.write(new DataOutputStream(mutatedOut)); + // InputStream mutated = new ByteArrayInputStream(mutatedOut.toByteArray()); - mutator.crossOver(mutated, inited, seed); + // mutator.crossOver(mutated, inited, seed); } catch (Exception e) { throw new RuntimeException("In method: " + method, e); } @@ -98,9 +99,8 @@ void allTests(FuzzedDataProvider data) throws Throwable { @Solo @SelfFuzzTest void fuzzStrings_torc(@NotNull String s0) { - if (s0.equals( - "qbfdbas891rbujebfrldsafbhd81irbewhavfsgdalvfdlasuvfzu3vrvesadolufdvsalfv" - + " dsagolufvdgsalfvffdshakfvdsakfvdksavfgdksafdasgfzdasgfzdasgfdsafdsafdasvfdavsfdasfdasvfdsafdsafdsafdsafdsafdsafdasfdasfdsafdsafdsa")) { + System.err.println(" =>>> FUZZ TEST: " + s0); + if (s0.equals("qbfdbas891rbujebfrldsafbhd81")) { throw new RuntimeException("Found it!"); } } diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java index 2fe5f71c7..b3eaa500d 100644 --- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java +++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java @@ -521,6 +521,7 @@ public static int startUnchainedFuzzer(Map args) { // should stop is used to signal the fuzzer thread to stop boolean shouldStop = false; + boolean threadFinished = false; Thread fuzzerThread = new Thread( @@ -674,23 +675,28 @@ public static int startUnchainedFuzzer(Map args) { try { Thread.sleep(maxDuration * 1000); } catch (InterruptedException e) { - e.printStackTrace(); + System.err.println("Duration thread stopping"); + return; } System.err.println("Max duration reached, stopping"); fuzzerThread.interrupt(); }); - durationThread.setDaemon(true); durationThread.start(); } try { fuzzerThread.join(); } catch (InterruptedException e) { - // stop duration thread if (durationThread != null) durationThread.interrupt(); e.printStackTrace(); + } finally { + if (durationThread != null) durationThread.interrupt(); + } + try { + durationThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); } - return 0; } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorFactory.java index 32f8852c3..3f1def1e3 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorFactory.java @@ -192,7 +192,8 @@ public Optional> tryCreate( } else { fixUpUtf8(bytes); } - return new String(bytes, StandardCharsets.UTF_8); + String result = new String(bytes, StandardCharsets.UTF_8); + return result; }, string -> string.getBytes(StandardCharsets.UTF_8), (Predicate inCycle) -> "String"); diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel index 6b45ad778..dd04a1249 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel @@ -18,6 +18,7 @@ java_library( "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc", "//src/main/java/com/code_intelligence/jazzer/mutation/support", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java index 8c72e861c..1891cf062 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutatorFactory.java @@ -24,7 +24,9 @@ import com.code_intelligence.jazzer.mutation.api.PseudoRandom; import com.code_intelligence.jazzer.mutation.api.SerializingMutator; import com.code_intelligence.jazzer.mutation.mutator.torc.Torc; +import com.code_intelligence.jazzer.mutation.mutator.torc.Torc.Pair; import com.code_intelligence.jazzer.mutation.support.RandomSupport; +import com.google.common.primitives.Bytes; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import java.io.DataInputStream; @@ -47,12 +49,12 @@ private interface MutationFunction { } private static final MutationFunction[] MUTATION_FUNCTIONS = { - LibFuzzerMutator::deleteRandomChunk, + /* LibFuzzerMutator::deleteRandomChunk, LibFuzzerMutator::insertRandomByte, LibFuzzerMutator::insertRepeatedBytes, LibFuzzerMutator::changeByte, - LibFuzzerMutator::changeBit, - LibFuzzerMutator::insertTorcBytes + LibFuzzerMutator::changeBit, */ + LibFuzzerMutator::replaceChunkByTorc }; @CheckReturnValue @@ -107,6 +109,7 @@ public byte[] detach(byte[] value) { @Override public byte[] init(PseudoRandom prng) { + System.err.println(" INIT!!!"); int len = prng.closedRange(minInitialSize(), maxInitialSize()); byte[] bytes = new byte[len]; prng.bytes(bytes); @@ -197,20 +200,63 @@ public static byte[] insertRepeatedBytes(byte[] value, int maxSizeIncrease, Pseu return out; } - public static byte[] insertTorcBytes(byte[] value, int maxSizeIncrease, PseudoRandom prng) { - if (maxSizeIncrease == 0) { + /** + * Insert a chunk from the Torc ring buffer into the input at a random position. If the Torc + * ring buffer is empty, return the input unchanged. If the Torc chunk is larger than the input, + * resize the chunk to fit into the input at the chosen position. + */ + public static byte[] replaceChunkByTorc(byte[] value, int maxSizeIncrease, PseudoRandom prng) { + Pair torcValues = Torc.get(prng); + if (torcValues == null) { + System.err.println(" --- Torc returned null or empty byte array"); return value; } - byte[] torcBytes = Torc.get(prng); - if (torcBytes == null || torcBytes.length == 0) { + + byte[] torcA = torcValues.operand1; + if (torcA == null || torcA.length == 0) { + System.err.println(" --- Torc A returned null or empty byte array"); return value; } - int insertPos = prng.indexIn(value.length + 1); - int insertSize = Math.min(torcBytes.length, maxSizeIncrease); - byte[] out = new byte[value.length + insertSize]; - System.arraycopy(value, 0, out, 0, insertPos); - System.arraycopy(torcBytes, 0, out, insertPos, insertSize); - System.arraycopy(value, insertPos, out, insertPos + insertSize, value.length - insertPos); + byte[] torcB = torcValues.operand2; + if (torcB == null || torcB.length == 0) { + System.err.println(" --- Torc B returned null or empty byte array"); + return value; + } + + System.err.println( + " --- Torc returned byte arrays of length " + torcA.length + " and " + torcB.length); + // also values + System.err.println(" -- input: " + Arrays.toString(value)); + System.err.println(" --- Torc A: " + Arrays.toString(torcA)); + System.err.println(" --- Torc B: " + Arrays.toString(torcB)); + + // overwrite + if (value.length == 0) { + return value; + } + int pos = prng.indexIn(value.length); + byte[] torcBytes = torcB; + + // Try finding out if the input value contains torcA as a subsequence. + int aOffset = Bytes.indexOf(value, torcA); + + if (aOffset >= 0) { + System.err.println(" --- [TORC] Torc found subsequence at offset " + aOffset); + pos = aOffset; + } else { + int bOffset = Bytes.indexOf(value, torcB); + if (bOffset >= 0) { + System.err.println(" --- [TORC] Torc found subsequence at offset " + bOffset); + pos = bOffset; + torcBytes = torcA; + } + } + + int newSize = + Math.min(torcBytes.length, Math.max(value.length - pos, value.length + maxSizeIncrease)); + byte[] out = Arrays.copyOf(value, newSize); + System.arraycopy(torcBytes, 0, out, pos, newSize - pos); + System.err.println(" --- Torc overwrote " + newSize + " bytes at position " + pos); return out; } diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java index a8b253a7c..1248be1b9 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/torc/Torc.java @@ -17,26 +17,52 @@ package com.code_intelligence.jazzer.mutation.mutator.torc; import com.code_intelligence.jazzer.mutation.api.PseudoRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; /** * Table of recent comparisons (Torc) mutator using the Torc library. Uses ring buffer to store * recent comparisons. */ public class Torc { - // ring buffer - private static final int SIZE = 1024; - private static final byte[][] buffer = new byte[SIZE][]; + // ring buffer containing recent comparisons + private static final int SIZE = 128; + private static List torc = new ArrayList<>(SIZE); private static int index = 0; + private static int count = 0; private Torc() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } - public static void add(byte[] data) { - buffer[index++ % SIZE] = data; + public static void add(byte[] data1, byte[] data2) { + if (data1 == null || data1.length == 0 || data2 == null || data2.length == 0) { + return; + } + System.err.println("[TORC] Adding comparison: " + Arrays.toString(data1) + " vs " + Arrays.toString(data2)); + Pair p = new Pair(data1, data2); + if (torc.size() < SIZE) { + torc.add(p); + } else { + torc.set(index, p); + index = (index + 1) % SIZE; + } } - public static byte[] get(PseudoRandom prng) { - return prng.pickIn(buffer); + public static Pair get(PseudoRandom prng) { + int index = prng.indexIn(torc.size()); + System.err.println("Torc returning index " + index); + return torc.get(index); + } + + public static class Pair { + public final byte[] operand1; + public final byte[] operand2; + + Pair(byte[] a, byte[] b) { + this.operand1 = a; + this.operand2 = b; + } } } diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java index c5f5b2756..e8ca70448 100644 --- a/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java +++ b/src/main/java/com/code_intelligence/jazzer/runtime/TraceDataFlowNativeCallbacks.java @@ -46,25 +46,15 @@ public final class TraceDataFlowNativeCallbacks { // public static native void traceMemcmp(byte[] b1, byte[] b2, int result, int pc); public static void traceMemcmp(byte[] b1, byte[] b2, int result, int pc) { - Torc.add(b1); - Torc.add(b2); - // System.out.println("traceMemcmp called with: "+ Arrays.toString(b1)+ "," - // +Arrays.toString(b2)+" , "+result+" , "+pc); - + Torc.add(b1, b2); } public static void traceStrcmp(String s1, String s2, int result, int pc) { - if (NATIVE_INITIALIZED) { - // System.out.println("traceStrcmp called with: "+ s1+" , "+s2+" , "+result+" , "+pc); - traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc); - } + traceMemcmp(encodeForLibFuzzer(s1), encodeForLibFuzzer(s2), result, pc); } public static void traceStrstr(String s1, String s2, int pc) { - if (NATIVE_INITIALIZED) { - // System.out.println("---traceStrstr called with: "+ s1+" , "+s2+" , "+pc); - traceStrstr0(encodeForLibFuzzer(s2), pc); - } + traceStrstr0(encodeForLibFuzzer(s2), pc); } public static void traceReflectiveCall(Executable callee, int pc) { @@ -88,9 +78,11 @@ public static int traceCmpLongWrapper(long arg1, long arg2, int pc) { // The caller has to ensure that arg1 and arg2 have the same class. public static void traceGenericCmp(Object arg1, Object arg2, int pc) { + System.err.println("[] -- Generic CMP " + arg1 + " vs " + arg2); if (arg1 instanceof CharSequence) { traceStrcmp(arg1.toString(), arg2.toString(), 1, pc); } else if (arg1 instanceof Integer) { + traceCmpInt((int) arg1, (int) arg2, pc); } else if (arg1 instanceof Long) { traceCmpLong((long) arg1, (long) arg2, pc); @@ -141,7 +133,7 @@ public static void traceGep(long val, int pc) { private static byte[] encodeForLibFuzzer(String str) { // libFuzzer string hooks only ever consume the first 64 bytes, so we can definitely cut the // string off after 64 characters. - return str.substring(0, Math.min(str.length(), 64)).getBytes(FUZZED_DATA_CHARSET); + /* */ return str.getBytes(FUZZED_DATA_CHARSET); } private static native void traceStrstr0(byte[] needle, int pc);