aboutsummaryrefslogtreecommitdiff
path: root/driver/coverage_tracker.cpp
blob: 0a576085beb474ec4fdb49cde26dec27cfebaa34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// 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