diff options
author | Fabian Meumertzheim <meumertzheim@code-intelligence.com> | 2023-02-15 11:35:03 +0100 |
---|---|---|
committer | Fabian Meumertzheim <fabian@meumertzhe.im> | 2023-02-17 14:23:30 +0100 |
commit | f10efc3c9a4a5b3d8c07da3ed85290feef958587 (patch) | |
tree | 972ee6b10e5fa9531db8c4a5a0e405942c3a5298 | |
parent | ae43d474e64263dfec3e8b0710db0c6409435705 (diff) | |
download | jazzer-api-f10efc3c9a4a5b3d8c07da3ed85290feef958587.tar.gz |
mutation: Actually implement the byte[] mutator
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(); } |