• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 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.code_intelligence.jazzer.driver;
18 
19 import com.code_intelligence.jazzer.utils.Log;
20 import java.io.BufferedReader;
21 import java.io.IOException;
22 import java.io.InputStreamReader;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.stream.Collectors;
29 
30 final class ReproducerTemplate {
31   // A constant pool CONSTANT_Utf8_info entry should be able to hold data of size
32   // uint16, but somehow this does not seem to be the case and leads to invalid
33   // code crash reproducer code. Reducing the size by one resolves the problem.
34   private static final int DATA_CHUNK_MAX_LENGTH = Short.MAX_VALUE - 1;
35   private static final String RAW_BYTES_INPUT =
36       "byte[] input = java.util.Base64.getDecoder().decode(base64Bytes);";
37   private static final String FUZZED_DATA_PROVIDER_INPUT =
38       "com.code_intelligence.jazzer.api.CannedFuzzedDataProvider input = new com.code_intelligence.jazzer.api.CannedFuzzedDataProvider(base64Bytes);";
39 
40   private final String targetClass;
41   private final boolean useFuzzedDataProvider;
42 
ReproducerTemplate(String targetClass, boolean useFuzzedDataProvider)43   public ReproducerTemplate(String targetClass, boolean useFuzzedDataProvider) {
44     this.targetClass = targetClass;
45     this.useFuzzedDataProvider = useFuzzedDataProvider;
46   }
47 
48   /**
49    * Emits a Java reproducer to {@code Crash_HASH.java} in {@code Opt.reproducerPath}.
50    *
51    * @param data the Base64-encoded data to emit as a string literal
52    * @param sha the SHA1 hash of the raw fuzzer input
53    */
dumpReproducer(String data, String sha)54   public void dumpReproducer(String data, String sha) {
55     String targetArg = useFuzzedDataProvider ? FUZZED_DATA_PROVIDER_INPUT : RAW_BYTES_INPUT;
56     String template = new BufferedReader(
57         new InputStreamReader(ReproducerTemplate.class.getResourceAsStream("Reproducer.java.tmpl"),
58             StandardCharsets.UTF_8))
59                           .lines()
60                           .collect(Collectors.joining("\n"));
61     String chunkedData = chunkStringLiteral(data);
62     String javaSource = String.format(template, sha, chunkedData, targetClass, targetArg);
63     Path javaPath = Paths.get(Opt.reproducerPath, String.format("Crash_%s.java", sha));
64     try {
65       Files.write(javaPath, javaSource.getBytes(StandardCharsets.UTF_8));
66     } catch (IOException e) {
67       Log.error(String.format("Failed to write Java reproducer to %s%n", javaPath));
68       e.printStackTrace();
69     }
70     Log.println(String.format(
71         "reproducer_path='%s'; Java reproducer written to %s%n", Opt.reproducerPath, javaPath));
72   }
73 
74   // The serialization of recorded FuzzedDataProvider invocations can get too long to be emitted
75   // into the template as a single String literal. This is mitigated by chunking the data and
76   // concatenating it again in the generated code.
chunkStringLiteral(String data)77   private String chunkStringLiteral(String data) {
78     ArrayList<String> chunks = new ArrayList<>();
79     for (int i = 0; i <= data.length() / DATA_CHUNK_MAX_LENGTH; i++) {
80       chunks.add(data.substring(
81           i * DATA_CHUNK_MAX_LENGTH, Math.min((i + 1) * DATA_CHUNK_MAX_LENGTH, data.length())));
82     }
83     return String.join("\", \"", chunks);
84   }
85 }
86