Skip to content

Commit d164e3c

Browse files
committed
feat: String mutator now uses @DictionaryProvider
The mutator now extracts applicable Strings from the provider and uses them during mutation according to the pInv of the last @DictionaryProvider annotation it found on this type.
1 parent 6cb10f8 commit d164e3c

File tree

4 files changed

+199
-2
lines changed

4 files changed

+199
-2
lines changed

src/main/java/com/code_intelligence/jazzer/mutation/mutator/lang/StringMutatorFactory.java

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,31 @@
1717
package com.code_intelligence.jazzer.mutation.mutator.lang;
1818

1919
import static com.code_intelligence.jazzer.mutation.combinator.MutatorCombinators.mutateThenMapToImmutable;
20-
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.*;
20+
import static com.code_intelligence.jazzer.mutation.support.DictionaryProviderSupport.extractLastInvProbability;
21+
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.findFirstParentIfClass;
22+
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.notNull;
23+
import static com.code_intelligence.jazzer.mutation.support.TypeSupport.withLength;
2124

2225
import com.code_intelligence.jazzer.mutation.annotation.Ascii;
2326
import com.code_intelligence.jazzer.mutation.annotation.UrlSegment;
2427
import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length;
2528
import com.code_intelligence.jazzer.mutation.api.Debuggable;
2629
import com.code_intelligence.jazzer.mutation.api.ExtendedMutatorFactory;
2730
import com.code_intelligence.jazzer.mutation.api.MutatorFactory;
31+
import com.code_intelligence.jazzer.mutation.api.PseudoRandom;
2832
import com.code_intelligence.jazzer.mutation.api.SerializingMutator;
2933
import com.code_intelligence.jazzer.mutation.mutator.libfuzzer.LibFuzzerMutatorFactory;
3034
import com.code_intelligence.jazzer.mutation.runtime.MutatorRuntime;
35+
import com.code_intelligence.jazzer.mutation.support.DictionaryProviderSupport;
3136
import com.code_intelligence.jazzer.mutation.support.TypeHolder;
37+
import java.io.DataInputStream;
38+
import java.io.DataOutputStream;
39+
import java.io.IOException;
3240
import java.lang.reflect.AnnotatedType;
3341
import java.nio.charset.StandardCharsets;
3442
import java.util.Optional;
3543
import java.util.function.Predicate;
44+
import java.util.stream.Stream;
3645

3746
final class StringMutatorFactory implements MutatorFactory {
3847
private static final int HEADER_MASK = 0b1100_0000;
@@ -177,7 +186,9 @@ public Optional<SerializingMutator<?>> tryCreate(
177186

178187
AnnotatedType innerByteArray =
179188
notNull(withLength(new TypeHolder<byte[]>() {}.annotatedType(), min, max));
180-
return LibFuzzerMutatorFactory.tryCreate(runtime, innerByteArray);
189+
Optional<SerializingMutator<?>> innerMutator =
190+
LibFuzzerMutatorFactory.tryCreate(runtime, innerByteArray);
191+
return UserDictionaryMutatorWrapper.of(runtime, innerMutator, type, min, max);
181192
})
182193
.map(
183194
byteArrayMutator -> {
@@ -199,4 +210,102 @@ public Optional<SerializingMutator<?>> tryCreate(
199210
(Predicate<Debuggable> inCycle) -> "String");
200211
});
201212
}
213+
214+
private static final class UserDictionaryMutatorWrapper extends SerializingMutator<byte[]> {
215+
private final byte[][] dictionaryValues;
216+
private final SerializingMutator<byte[]> basicMutator;
217+
private final int pInv;
218+
219+
public static Optional<SerializingMutator<?>> of(
220+
MutatorRuntime runtime,
221+
Optional<SerializingMutator<?>> mutator,
222+
AnnotatedType type,
223+
int minSize,
224+
int maxSize) {
225+
if (!mutator.isPresent()) {
226+
return Optional.empty();
227+
}
228+
SerializingMutator<byte[]> castedMutator =
229+
mutator.map(m -> (SerializingMutator<byte[]>) m).get();
230+
Optional<byte[][]> values = generateDictionaryValues(runtime, type, minSize, maxSize);
231+
if (!values.isPresent()) {
232+
return mutator;
233+
}
234+
return Optional.of(
235+
new UserDictionaryMutatorWrapper(
236+
castedMutator, values.get(), extractLastInvProbability(type)));
237+
}
238+
239+
public UserDictionaryMutatorWrapper(
240+
SerializingMutator<byte[]> basicMutator, byte[][] dictionaryValues, int pInv) {
241+
this.basicMutator = basicMutator;
242+
this.dictionaryValues = dictionaryValues;
243+
this.pInv = pInv;
244+
}
245+
246+
public static Optional<byte[][]> generateDictionaryValues(
247+
MutatorRuntime runtime, AnnotatedType type, int minSize, int maxSize) {
248+
return DictionaryProviderSupport.extractProviderStreams(runtime, type)
249+
.map(
250+
stream ->
251+
stream
252+
.flatMap(
253+
o -> {
254+
if (o instanceof String) {
255+
return Stream.of(((String) o).getBytes(StandardCharsets.UTF_8));
256+
} else {
257+
return Stream.empty();
258+
}
259+
})
260+
.filter(b -> b.length >= minSize && b.length <= maxSize)
261+
.distinct()
262+
.toArray(byte[][]::new));
263+
}
264+
265+
@Override
266+
public String toDebugString(Predicate<Debuggable> isInCycle) {
267+
return "String";
268+
}
269+
270+
@Override
271+
public byte[] read(DataInputStream in) throws IOException {
272+
return basicMutator.read(in);
273+
}
274+
275+
@Override
276+
public void write(byte[] value, DataOutputStream out) throws IOException {
277+
basicMutator.write(value, out);
278+
}
279+
280+
@Override
281+
public byte[] detach(byte[] value) {
282+
return basicMutator.detach(value);
283+
}
284+
285+
@Override
286+
public byte[] init(PseudoRandom prng) {
287+
if (prng.trueInOneOutOf(pInv)) {
288+
return prng.pickIn(dictionaryValues);
289+
}
290+
return basicMutator.init(prng);
291+
}
292+
293+
@Override
294+
public byte[] mutate(byte[] value, PseudoRandom prng) {
295+
if (prng.trueInOneOutOf(pInv)) {
296+
return prng.pickIn(dictionaryValues);
297+
}
298+
return basicMutator.mutate(value, prng);
299+
}
300+
301+
@Override
302+
public byte[] crossOver(byte[] value, byte[] otherValue, PseudoRandom prng) {
303+
return basicMutator.crossOver(value, otherValue, prng);
304+
}
305+
306+
@Override
307+
public boolean hasFixedSize() {
308+
return false;
309+
}
310+
}
202311
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ java_library(
77
],
88
deps = [
99
"//src/main/java/com/code_intelligence/jazzer/mutation/annotation",
10+
"//src/main/java/com/code_intelligence/jazzer/mutation/runtime",
1011
"//src/main/java/com/code_intelligence/jazzer/mutation/utils",
1112
"//src/main/java/com/code_intelligence/jazzer/utils:log",
1213
],

tests/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ java_fuzz_target_test(
2020
verify_crash_input = False,
2121
)
2222

23+
java_fuzz_target_test(
24+
name = "DictionaryProviderFuzzerLongString",
25+
srcs = [
26+
"src/test/java/com/example/DictionaryProviderFuzzerLongString.java",
27+
],
28+
allowed_findings = ["com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow"],
29+
fuzzer_args = [
30+
"-runs=10000",
31+
],
32+
target_class = "com.example.DictionaryProviderFuzzerLongString",
33+
verify_crash_input = False,
34+
verify_crash_reproducer = False,
35+
deps = [
36+
"//deploy:jazzer-junit",
37+
"//deploy:jazzer-project",
38+
"@maven//:org_junit_jupiter_junit_jupiter_api",
39+
"@maven//:org_junit_jupiter_junit_jupiter_engine",
40+
"@maven//:org_junit_platform_junit_platform_launcher",
41+
],
42+
)
43+
2344
java_fuzz_target_test(
2445
name = "JpegImageParserAutofuzz",
2546
allowed_findings = ["java.lang.NegativeArraySizeException"],
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2024 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow;
20+
import com.code_intelligence.jazzer.junit.FuzzTest;
21+
import com.code_intelligence.jazzer.mutation.annotation.DictionaryProvider;
22+
import com.code_intelligence.jazzer.mutation.annotation.NotNull;
23+
import com.code_intelligence.jazzer.mutation.annotation.WithUtf8Length;
24+
import java.util.stream.Stream;
25+
26+
public class DictionaryProviderFuzzerLongString {
27+
28+
public static Stream<?> myDict() {
29+
return Stream.of(
30+
repeat("0123456789abcdef", 50),
31+
repeat("sitting duck suprime", 53),
32+
// We can mix all kinds of values in the same dictionary.
33+
// Each mutator only takes the values it can use.
34+
123);
35+
}
36+
37+
@FuzzTest
38+
// Just propagate the dictionary to all types of the fuzz test method that can use it.
39+
// We could also only annotate the String parameters, but this is easier.
40+
@DictionaryProvider(
41+
value = {"myDict"},
42+
// Don't want to wait, force String mutators to use dictionary values every other time.
43+
pInv = 2)
44+
public static void fuzzerTestOneInput(
45+
@NotNull @WithUtf8Length(max = 10000) String data,
46+
@NotNull @WithUtf8Length(max = 10000) String data2) {
47+
/*
48+
* libFuzzer's table of recent compares only allows 64 bytes, so asking the fuzzer to construct
49+
* these long strings would run for a very very long time without finding them. With a
50+
* DictionaryProvider this problem is trivial, because we can directly provide these long strings to
51+
* the fuzzer, and also force that they are used more often by setting pInv to a low value.
52+
*/
53+
if (data.equals(repeat("0123456789abcdef", 50))
54+
&& data2.equals(repeat("sitting duck suprime", 53))) {
55+
throw new FuzzerSecurityIssueLow("Found the long string!");
56+
}
57+
}
58+
59+
private static String repeat(String str, int count) {
60+
StringBuilder sb = new StringBuilder(str.length() * count);
61+
for (int i = 0; i < count; i++) {
62+
sb.append(str);
63+
}
64+
return sb.toString();
65+
}
66+
}

0 commit comments

Comments
 (0)