aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Meumertzheim <meumertzheim@code-intelligence.com>2023-02-15 11:35:03 +0100
committerFabian Meumertzheim <fabian@meumertzhe.im>2023-02-17 14:23:30 +0100
commitf10efc3c9a4a5b3d8c07da3ed85290feef958587 (patch)
tree972ee6b10e5fa9531db8c4a5a0e405942c3a5298
parentae43d474e64263dfec3e8b0710db0c6409435705 (diff)
downloadjazzer-api-f10efc3c9a4a5b3d8c07da3ed85290feef958587.tar.gz
mutation: Actually implement the byte[] mutator
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java5
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java5
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandom.java6
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel1
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java13
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel14
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutator.java81
-rw-r--r--src/main/java/com/code_intelligence/jazzer/mutation/support/RandomSupport.java40
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel10
-rw-r--r--src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java34
-rw-r--r--src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel9
-rw-r--r--src/main/native/com/code_intelligence/jazzer/driver/mutator.cpp31
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel1
-rw-r--r--src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java8
14 files changed, 255 insertions, 3 deletions
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java
index b4577fd8..b93e843f 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/ArgumentsMutator.java
@@ -115,6 +115,11 @@ public final class ArgumentsMutator {
}
public void invoke() throws Throwable {
+ // TODO: Sometimes hash the serialized value before and after the invocation and check that the
+ // hashes match to catch fuzz tests that mutate mutable inputs (e.g. byte[]).
+ // Alternatively, always detach arguments and instead of the SafeToMutate annotation have a
+ // Mutable annotation that can be used to e.g. receive a mutable implementation of List. This
+ // is always safe, but could incur additional overhead for arrays.
try {
method.invoke(instance, productMutator.detachSelectively(arguments, shouldDetach));
} catch (IllegalAccessException e) {
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java b/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java
index dbd0f956..1395df92 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/api/PseudoRandom.java
@@ -70,4 +70,9 @@ public interface PseudoRandom {
* {@code [lowerInclusive, upperInclusive]}.
*/
long closedRange(long lowerInclusive, long upperInclusive);
+
+ /**
+ * Fills the given array with random bytes.
+ */
+ void bytes(byte[] bytes);
}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandom.java b/src/main/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandom.java
index de87ce4e..48eed211 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandom.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/engine/SeededPseudoRandom.java
@@ -19,6 +19,7 @@ package com.code_intelligence.jazzer.mutation.engine;
import static com.code_intelligence.jazzer.mutation.support.Preconditions.require;
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
+import com.code_intelligence.jazzer.mutation.support.RandomSupport;
import java.util.List;
import java.util.SplittableRandom;
@@ -111,4 +112,9 @@ public final class SeededPseudoRandom implements PseudoRandom {
return r;
}
}
+
+ @Override
+ public void bytes(byte[] bytes) {
+ RandomSupport.nextBytes(random, bytes);
+ }
}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel
index faf62df8..77d5c087 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/BUILD.bazel
@@ -8,6 +8,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/libfuzzer",
"//src/main/java/com/code_intelligence/jazzer/mutation/support",
"@com_google_errorprone_error_prone_annotations//jar",
],
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java
index 7f10078e..ce5c1002 100644
--- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/ByteArrayMutatorFactory.java
@@ -23,6 +23,7 @@ import com.code_intelligence.jazzer.mutation.api.Debuggable;
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
+import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutator;
import com.google.errorprone.annotations.Immutable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -76,17 +77,23 @@ final class ByteArrayMutatorFactory extends MutatorFactory {
@Override
public byte[] init(PseudoRandom prng) {
- throw new UnsupportedOperationException("not implemented");
+ // TODO: Improve the way the upper bound is determined, e.g. grow it over time and/or add
+ // support for the WithSize annotation.
+ byte[] bytes = new byte[prng.indexIn(1000)];
+ prng.bytes(bytes);
+ return bytes;
}
@Override
public byte[] mutate(byte[] value, PseudoRandom prng) {
- throw new UnsupportedOperationException("not implemented");
+ // TODO: The way maxSizeIncrease is determined is just a heuristic and hasn't been
+ // benchmarked.
+ return LibFuzzerMutator.mutateDefault(value, Math.max(8, value.length / 16));
}
@Override
public String toDebugString(Predicate<Debuggable> isInCycle) {
- return "ByteArray";
+ return "byte[]";
}
}
}
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
new file mode 100644
index 00000000..5caeb893
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/BUILD.bazel
@@ -0,0 +1,14 @@
+java_library(
+ name = "libfuzzer",
+ srcs = ["LibFuzzerMutator.java"],
+ visibility = [
+ # libFuzzer's mutators should only by used by mutators for primitive types as we want to get
+ # rid of this dependency eventually.
+ "//src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang:__pkg__",
+ ],
+ deps = [
+ "//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",
+ ],
+)
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutator.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutator.java
new file mode 100644
index 00000000..65b4f8fd
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/libfuzzer/LibFuzzerMutator.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 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.Preconditions.require;
+
+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;
+import java.util.Arrays;
+
+public final class LibFuzzerMutator {
+ public static byte[] mutateDefault(byte[] data, int maxSizeIncrease) {
+ byte[] mutatedBytes;
+ if (maxSizeIncrease == 0) {
+ mutatedBytes = data;
+ } 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);
+ }
+
+ public static <T> T mutateDefault(T value, Serializer<T> serializer, int maxSizeIncrease) {
+ require(maxSizeIncrease >= 0);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ serializer.writeExclusive(value, out);
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "writeExclusive is not expected to throw if the underlying stream doesn't", e);
+ }
+
+ byte[] mutatedBytes = mutateDefault(out.toByteArray(), maxSizeIncrease);
+
+ try {
+ return serializer.readExclusive(new ByteArrayInputStream(mutatedBytes));
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "readExclusive is not expected to throw if the underlying stream doesn't", e);
+ }
+ }
+
+ private static int defaultMutate(byte[] buffer, int size) {
+ if (Mutator.SHOULD_MOCK) {
+ return defaultMutateMock(buffer, size);
+ } else {
+ return Mutator.defaultMutateNative(buffer, size);
+ }
+ }
+
+ private static int defaultMutateMock(byte[] buffer, int size) {
+ int newSize = (buffer.length + size) / 2;
+ for (int i = 0; i < newSize; i++) {
+ buffer[i] += i + 1;
+ }
+ return newSize;
+ }
+
+ private LibFuzzerMutator() {}
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/support/RandomSupport.java b/src/main/java/com/code_intelligence/jazzer/mutation/support/RandomSupport.java
new file mode 100644
index 00000000..a831016e
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/mutation/support/RandomSupport.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 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.support;
+
+import java.util.SplittableRandom;
+
+public final class RandomSupport {
+ private RandomSupport() {}
+
+ /**
+ * Polyfill for {@link SplittableRandom#nextBytes(byte[])}, which is not available in Java 8.
+ */
+ public static void nextBytes(SplittableRandom random, byte[] bytes) {
+ // Taken from the implementation contract
+ // https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/RandomGenerator.html#nextBytes(byte%5B%5D)
+ // for interoperability with the RandomGenerator interface available as of Java 17.
+ int i = 0;
+ int len = bytes.length;
+ for (int words = len >> 3; words-- > 0;) {
+ long rnd = random.nextLong();
+ for (int n = 8; n-- > 0; rnd >>>= Byte.SIZE) bytes[i++] = (byte) rnd;
+ }
+ if (i < len)
+ for (long rnd = random.nextLong(); i<len; rnd>>>= Byte.SIZE) bytes[i++] = (byte) rnd;
+ }
+}
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 87d9a588..95a64d4c 100644
--- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
@@ -126,6 +126,15 @@ 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 = [
@@ -143,6 +152,7 @@ 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/java/com/code_intelligence/jazzer/runtime/Mutator.java b/src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java
new file mode 100644
index 00000000..2d9a7f65
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/Mutator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.runtime;
+
+import com.github.fmeum.rules_jni.RulesJni;
+
+public final class Mutator {
+ public static final boolean SHOULD_MOCK =
+ Boolean.parseBoolean(System.getenv("JAZZER_MOCK_LIBFUZZER_MUTATOR"));
+
+ static {
+ if (!SHOULD_MOCK) {
+ RulesJni.loadLibrary("jazzer_driver", "/com/code_intelligence/jazzer/driver");
+ }
+ }
+
+ public static native int defaultMutateNative(byte[] buffer, int size);
+
+ private Mutator() {}
+}
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 dd8cd035..7e233b91 100644
--- a/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel
+++ b/src/main/native/com/code_intelligence/jazzer/driver/BUILD.bazel
@@ -28,6 +28,7 @@ cc_library(
":fuzz_target_runner",
":jazzer_fuzzer_callbacks",
":libfuzzer_callbacks",
+ ":mutator",
],
)
@@ -106,6 +107,14 @@ cc_library(
)
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"],
linkopts = ["-ldl"],
diff --git a/src/main/native/com/code_intelligence/jazzer/driver/mutator.cpp b/src/main/native/com/code_intelligence/jazzer/driver/mutator.cpp
new file mode 100644
index 00000000..4e21612b
--- /dev/null
+++ b/src/main/native/com/code_intelligence/jazzer/driver/mutator.cpp
@@ -0,0 +1,31 @@
+// Copyright 2023 Code Intelligence GmbH
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstddef>
+#include <cstdint>
+
+#include "com_code_intelligence_jazzer_runtime_Mutator.h"
+
+extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize);
+
+[[maybe_unused]] jint
+Java_com_code_1intelligence_jazzer_runtime_Mutator_defaultMutateNative(
+ JNIEnv *env, jclass, jbyteArray jni_data, jint size) {
+ jint maxSize = env->GetArrayLength(jni_data);
+ uint8_t *data =
+ static_cast<uint8_t *>(env->GetPrimitiveArrayCritical(jni_data, nullptr));
+ jint res = LLVMFuzzerMutate(data, size, maxSize);
+ env->ReleasePrimitiveArrayCritical(jni_data, data, 0);
+ return res;
+}
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel
index 5553c653..d8e587af 100644
--- a/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/mutator/BUILD.bazel
@@ -4,6 +4,7 @@ java_test_suite(
name = "MutatorTests",
size = "small",
srcs = glob(["*.java"]),
+ env = {"JAZZER_MOCK_LIBFUZZER_MUTATOR": "true"},
runner = "junit5",
deps = [
"//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
diff --git a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
index 4b3c0c76..0b486d08 100644
--- a/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
+++ b/src/test/java/com/code_intelligence/jazzer/mutation/support/TestSupport.java
@@ -187,6 +187,14 @@ public final class TestSupport {
}
@Override
+ public void bytes(byte[] bytes) {
+ assertThat(elements).isNotEmpty();
+ byte[] result = (byte[]) elements.poll();
+ assertThat(result).hasLength(bytes.length);
+ System.arraycopy(result, 0, bytes, 0, bytes.length);
+ }
+
+ @Override
public void close() {
assertThat(elements).isEmpty();
}