| // 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 |