blob: 0a576085beb474ec4fdb49cde26dec27cfebaa34 [file] [log] [blame]
// 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