From 683af3014482f7defb2ea8a380b2692df9500b9d Mon Sep 17 00:00:00 2001 From: Simon Resch Date: Fri, 7 Nov 2025 11:01:00 +0100 Subject: [PATCH 1/2] feat: sometimes interpret char[] mutations as single bytes When mutating char[] randomly interpret the bytes from libFuzzer as individual (single byte) chars. This helps to make use of libFuzzers table of recent compare entries (encoded as CESU8) if the char[] is used as a String inside the fuzz test. --- .../lang/PrimitiveArrayMutatorFactory.java | 34 ++++++++++++++++--- tests/BUILD.bazel | 10 ++++++ .../java/com/example/CharArrayFuzzer.java | 29 ++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 tests/src/test/java/com/example/CharArrayFuzzer.java diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java index dd321c72d..f5d4ab6b0 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java @@ -43,6 +43,7 @@ import java.lang.reflect.AnnotatedType; import java.nio.ByteBuffer; import java.util.Optional; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -81,6 +82,7 @@ public static final class PrimitiveArrayMutator extends SerializingMutator private final SerializingMutator innerMutator; private final Function toPrimitive; private final Function toBytes; + private final BiFunction toPrimitiveAfterMutate; @SuppressWarnings("unchecked") public PrimitiveArrayMutator(AnnotatedType type) { @@ -92,6 +94,8 @@ public PrimitiveArrayMutator(AnnotatedType type) { innerMutator = (SerializingMutator) LibFuzzerMutatorFactory.tryCreate(innerByteArray).get(); toPrimitive = (Function) makeBytesToPrimitiveArrayConverter(elementType); + toPrimitiveAfterMutate = + (BiFunction) makeBytesToPrimitiveArrayAfterMutate(elementType); toBytes = (Function) makePrimitiveArrayToBytesConverter(elementType); } @@ -128,14 +132,13 @@ public T init(PseudoRandom prng) { @Override public T mutate(T value, PseudoRandom prng) { - return (T) toPrimitive.apply(innerMutator.mutate(toBytes.apply(value), prng)); + return toPrimitiveAfterMutate.apply(innerMutator.mutate(toBytes.apply(value), prng), prng); } @Override public T crossOver(T value, T otherValue, PseudoRandom prng) { - return (T) - toPrimitive.apply( - innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng)); + return toPrimitive.apply( + innerMutator.crossOver(toBytes.apply(value), toBytes.apply(otherValue), prng)); } private void extractRange(AnnotatedType type) { @@ -250,6 +253,29 @@ private static AnnotatedType convertWithLength(AnnotatedType type, AnnotatedType } } + // Randomly maps the byte array from libFuzzer directly onto char[] or converts each byte into a + // 2 byte char. This helps in cases where a String is constructed out of char[] and libFuzzer + // inserts CESU8 encoded bytes into the byte[]. + public char[] postMutateChars(byte[] bytes, PseudoRandom prng) { + if (prng.choice()) { + return (char[]) toPrimitive.apply(bytes); + } else { + char[] chars = new char[bytes.length]; + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) bytes[i]; + } + return chars; + } + } + + public BiFunction makeBytesToPrimitiveArrayAfterMutate( + AnnotatedType type) { + if (type.getType().getTypeName().equals("char")) { + return this::postMutateChars; + } + return (bytes, ignored) -> makeBytesToPrimitiveArrayConverter(type).apply(bytes); + } + public static Function makePrimitiveArrayToBytesConverter(AnnotatedType type) { switch (type.getType().getTypeName()) { case "int": diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 7289f9d3a..88df62574 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1136,6 +1136,16 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "CharArrayFuzzer", + srcs = [ + "src/test/java/com/example/CharArrayFuzzer.java", + ], + allowed_findings = ["java.lang.RuntimeException"], + target_class = "com.example.CharArrayFuzzer", + verify_crash_reproducer = False, +) + filegroup( name = "fuzz_test_lister_classes", srcs = ["src/test/data/fuzz_test_lister_test"], diff --git a/tests/src/test/java/com/example/CharArrayFuzzer.java b/tests/src/test/java/com/example/CharArrayFuzzer.java new file mode 100644 index 000000000..685ef0589 --- /dev/null +++ b/tests/src/test/java/com/example/CharArrayFuzzer.java @@ -0,0 +1,29 @@ +/* + * 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.example; + +public class CharArrayFuzzer { + public static void fuzzerTestOneInput(char[] data) { + if (data == null) { + return; + } + String expression = new String(data); + if (expression.contains("jazzer")) { + throw new RuntimeException("found jazzer"); + } + } +} From 56692d3af20f5eecccd50b49f7e2f7fe00a6b88a Mon Sep 17 00:00:00 2001 From: Khaled Yakdan Date: Tue, 11 Nov 2025 15:11:32 +0100 Subject: [PATCH 2/2] WIP: decode bytes as CESU8 when converting to char[] --- .../mutator/lang/PrimitiveArrayMutatorFactory.java | 7 ++----- tests/src/test/java/com/example/CharArrayFuzzer.java | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java index f5d4ab6b0..85f3dc3ff 100644 --- a/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java +++ b/src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/PrimitiveArrayMutatorFactory.java @@ -42,6 +42,7 @@ import java.lang.reflect.AnnotatedArrayType; import java.lang.reflect.AnnotatedType; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; @@ -260,11 +261,7 @@ public char[] postMutateChars(byte[] bytes, PseudoRandom prng) { if (prng.choice()) { return (char[]) toPrimitive.apply(bytes); } else { - char[] chars = new char[bytes.length]; - for (int i = 0; i < chars.length; i++) { - chars[i] = (char) bytes[i]; - } - return chars; + return new String(bytes, Charset.forName("CESU-8")).toCharArray(); } } diff --git a/tests/src/test/java/com/example/CharArrayFuzzer.java b/tests/src/test/java/com/example/CharArrayFuzzer.java index 685ef0589..c1c3fda45 100644 --- a/tests/src/test/java/com/example/CharArrayFuzzer.java +++ b/tests/src/test/java/com/example/CharArrayFuzzer.java @@ -22,8 +22,8 @@ public static void fuzzerTestOneInput(char[] data) { return; } String expression = new String(data); - if (expression.contains("jazzer")) { - throw new RuntimeException("found jazzer"); + if (expression.equals("中 Bös3r \uD801\uDC00 C0d3 中")) { + throw new RuntimeException("Found evil code"); } } }