216 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
| // Copyright 2021 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 "coverage_tracker.h"
 | |
| 
 | |
| #include <jni.h>
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <memory>
 | |
| #include <stdexcept>
 | |
| 
 | |
| #include "absl/strings/str_format.h"
 | |
| 
 | |
| extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *start,
 | |
|                                                    uint8_t *end);
 | |
| extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
 | |
|                                          const uintptr_t *pcs_end);
 | |
| extern "C" size_t __sanitizer_cov_get_observed_pcs(uintptr_t **pc_entries);
 | |
| 
 | |
| constexpr auto kCoverageMapClass =
 | |
|     "com/code_intelligence/jazzer/runtime/CoverageMap";
 | |
| constexpr auto kByteBufferClass = "java/nio/ByteBuffer";
 | |
| constexpr auto kCoverageRecorderClass =
 | |
|     "com/code_intelligence/jazzer/instrumentor/CoverageRecorder";
 | |
| 
 | |
| // The initial size of the Java coverage map (512 counters).
 | |
| constexpr std::size_t kInitialCoverageCountersBufferSize = 1u << 9u;
 | |
| // The maximum size of the Java coverage map (1,048,576 counters).
 | |
| // Since the memory for the coverage map needs to be allocated contiguously,
 | |
| // increasing the maximum size incurs additional memory (but not runtime)
 | |
| // overhead for all fuzz targets.
 | |
| constexpr std::size_t kMaxCoverageCountersBufferSize = 1u << 20u;
 | |
| static_assert(kMaxCoverageCountersBufferSize <=
 | |
|               std::numeric_limits<jint>::max());
 | |
| 
 | |
| namespace {
 | |
| void AssertNoException(JNIEnv &env) {
 | |
|   if (env.ExceptionCheck()) {
 | |
|     env.ExceptionDescribe();
 | |
|     throw std::runtime_error(
 | |
|         "Java exception occurred in CoverageTracker JNI code");
 | |
|   }
 | |
| }
 | |
| }  // namespace
 | |
| 
 | |
| namespace jazzer {
 | |
| 
 | |
| uint8_t *CoverageTracker::counters_ = nullptr;
 | |
| uint32_t *CoverageTracker::fake_instructions_ = nullptr;
 | |
| PCTableEntry *CoverageTracker::pc_entries_ = nullptr;
 | |
| 
 | |
| void CoverageTracker::Setup(JNIEnv &env) {
 | |
|   if (counters_ != nullptr) {
 | |
|     throw std::runtime_error(
 | |
|         "CoverageTracker::Setup must not be called more than once");
 | |
|   }
 | |
|   JNINativeMethod coverage_tracker_native_methods[]{
 | |
|       {(char *)"registerNewCoverageCounters", (char *)"()V",
 | |
|        (void *)&RegisterNewCoverageCounters},
 | |
|   };
 | |
|   jclass coverage_map = env.FindClass(kCoverageMapClass);
 | |
|   env.RegisterNatives(coverage_map, coverage_tracker_native_methods, 1);
 | |
| 
 | |
|   // libFuzzer requires an array containing the instruction addresses associated
 | |
|   // with the coverage counters registered above. Given that we are
 | |
|   // instrumenting Java code, we need to synthesize addresses that are known not
 | |
|   // to conflict with any valid instruction address in native code. Just like
 | |
|   // atheris we ensure there are no collisions by using the addresses of an
 | |
|   // allocated buffer. Note: We intentionally never deallocate the allocations
 | |
|   // made here as they have static lifetime and we can't guarantee they wouldn't
 | |
|   // be freed before libFuzzer stops using them.
 | |
|   constexpr std::size_t counters_size = kMaxCoverageCountersBufferSize;
 | |
|   counters_ = new uint8_t[counters_size];
 | |
|   Clear();
 | |
| 
 | |
|   // Never deallocated, see above.
 | |
|   fake_instructions_ = new uint32_t[counters_size];
 | |
|   std::fill(fake_instructions_, fake_instructions_ + counters_size, 0);
 | |
| 
 | |
|   // Never deallocated, see above.
 | |
|   pc_entries_ = new PCTableEntry[counters_size];
 | |
|   for (std::size_t i = 0; i < counters_size; ++i) {
 | |
|     pc_entries_[i].PC = reinterpret_cast<uintptr_t>(fake_instructions_ + i);
 | |
|     // TODO: Label Java PCs corresponding to functions as such.
 | |
|     pc_entries_[i].PCFlags = 0;
 | |
|   }
 | |
| 
 | |
|   // Register the first batch of coverage counters.
 | |
|   RegisterNewCoverageCounters(env, nullptr);
 | |
| }
 | |
| 
 | |
| void JNICALL CoverageTracker::RegisterNewCoverageCounters(JNIEnv &env,
 | |
|                                                           jclass cls) {
 | |
|   jclass coverage_map = env.FindClass(kCoverageMapClass);
 | |
|   AssertNoException(env);
 | |
|   jfieldID counters_buffer_id = env.GetStaticFieldID(
 | |
|       coverage_map, "mem", absl::StrFormat("L%s;", kByteBufferClass).c_str());
 | |
|   AssertNoException(env);
 | |
|   jobject counters_buffer =
 | |
|       env.GetStaticObjectField(coverage_map, counters_buffer_id);
 | |
|   AssertNoException(env);
 | |
| 
 | |
|   jclass byte_buffer = env.FindClass(kByteBufferClass);
 | |
|   AssertNoException(env);
 | |
|   jmethodID byte_buffer_capacity_id =
 | |
|       env.GetMethodID(byte_buffer, "capacity", "()I");
 | |
|   AssertNoException(env);
 | |
|   jint old_counters_buffer_size =
 | |
|       env.CallIntMethod(counters_buffer, byte_buffer_capacity_id);
 | |
|   AssertNoException(env);
 | |
| 
 | |
|   jint new_counters_buffer_size;
 | |
|   if (old_counters_buffer_size == 0) {
 | |
|     new_counters_buffer_size = kInitialCoverageCountersBufferSize;
 | |
|   } else {
 | |
|     new_counters_buffer_size = 2 * old_counters_buffer_size;
 | |
|     if (new_counters_buffer_size > kMaxCoverageCountersBufferSize) {
 | |
|       throw std::runtime_error(
 | |
|           "Maximal size of the coverage counters buffer exceeded");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   jobject new_counters_buffer = env.NewDirectByteBuffer(
 | |
|       static_cast<void *>(counters_), new_counters_buffer_size);
 | |
|   AssertNoException(env);
 | |
|   env.SetStaticObjectField(coverage_map, counters_buffer_id,
 | |
|                            new_counters_buffer);
 | |
|   AssertNoException(env);
 | |
| 
 | |
|   // Register only the new second half of the counters buffer with libFuzzer.
 | |
|   __sanitizer_cov_8bit_counters_init(counters_ + old_counters_buffer_size,
 | |
|                                      counters_ + new_counters_buffer_size);
 | |
|   __sanitizer_cov_pcs_init(
 | |
|       (uintptr_t *)(pc_entries_ + old_counters_buffer_size),
 | |
|       (uintptr_t *)(pc_entries_ + new_counters_buffer_size));
 | |
| }
 | |
| 
 | |
| void CoverageTracker::Clear() {
 | |
|   std::fill(counters_, counters_ + kMaxCoverageCountersBufferSize, 0);
 | |
| }
 | |
| 
 | |
| uint8_t *CoverageTracker::GetCoverageCounters() { return counters_; }
 | |
| 
 | |
| void CoverageTracker::RecordInitialCoverage(JNIEnv &env) {
 | |
|   jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
 | |
|   AssertNoException(env);
 | |
|   jmethodID coverage_recorder_update_covered_ids_with_coverage_map =
 | |
|       env.GetStaticMethodID(coverage_recorder,
 | |
|                             "updateCoveredIdsWithCoverageMap", "()V");
 | |
|   AssertNoException(env);
 | |
|   env.CallStaticVoidMethod(
 | |
|       coverage_recorder,
 | |
|       coverage_recorder_update_covered_ids_with_coverage_map);
 | |
|   AssertNoException(env);
 | |
| }
 | |
| 
 | |
| void CoverageTracker::ReplayInitialCoverage(JNIEnv &env) {
 | |
|   jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
 | |
|   AssertNoException(env);
 | |
|   jmethodID coverage_recorder_update_covered_ids_with_coverage_map =
 | |
|       env.GetStaticMethodID(coverage_recorder, "replayCoveredIds", "()V");
 | |
|   AssertNoException(env);
 | |
|   env.CallStaticVoidMethod(
 | |
|       coverage_recorder,
 | |
|       coverage_recorder_update_covered_ids_with_coverage_map);
 | |
|   AssertNoException(env);
 | |
| }
 | |
| 
 | |
| std::string CoverageTracker::ComputeCoverage(JNIEnv &env) {
 | |
|   uintptr_t *covered_pcs;
 | |
|   size_t num_covered_pcs = __sanitizer_cov_get_observed_pcs(&covered_pcs);
 | |
|   std::vector<jint> covered_edge_ids{};
 | |
|   covered_edge_ids.reserve(num_covered_pcs);
 | |
|   const uintptr_t first_pc = pc_entries_[0].PC;
 | |
|   std::for_each(covered_pcs, covered_pcs + num_covered_pcs,
 | |
|                 [&covered_edge_ids, first_pc](const uintptr_t pc) {
 | |
|                   jint edge_id =
 | |
|                       (pc - first_pc) / sizeof(fake_instructions_[0]);
 | |
|                   covered_edge_ids.push_back(edge_id);
 | |
|                 });
 | |
|   delete[] covered_pcs;
 | |
| 
 | |
|   jclass coverage_recorder = env.FindClass(kCoverageRecorderClass);
 | |
|   AssertNoException(env);
 | |
|   jmethodID coverage_recorder_compute_file_coverage = env.GetStaticMethodID(
 | |
|       coverage_recorder, "computeFileCoverage", "([I)Ljava/lang/String;");
 | |
|   AssertNoException(env);
 | |
|   jintArray covered_edge_ids_jni = env.NewIntArray(num_covered_pcs);
 | |
|   AssertNoException(env);
 | |
|   env.SetIntArrayRegion(covered_edge_ids_jni, 0, num_covered_pcs,
 | |
|                         covered_edge_ids.data());
 | |
|   AssertNoException(env);
 | |
|   auto file_coverage_jni = (jstring)(env.CallStaticObjectMethod(
 | |
|       coverage_recorder, coverage_recorder_compute_file_coverage,
 | |
|       covered_edge_ids_jni));
 | |
|   AssertNoException(env);
 | |
|   auto file_coverage_cstr = env.GetStringUTFChars(file_coverage_jni, nullptr);
 | |
|   AssertNoException(env);
 | |
|   std::string file_coverage(file_coverage_cstr);
 | |
|   env.ReleaseStringUTFChars(file_coverage_jni, file_coverage_cstr);
 | |
|   AssertNoException(env);
 | |
|   return file_coverage;
 | |
| }
 | |
| }  // namespace jazzer
 |