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
|