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