aboutsummaryrefslogtreecommitdiff
path: root/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp')
-rw-r--r--src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp313
1 files changed, 313 insertions, 0 deletions
diff --git a/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp
new file mode 100644
index 00000000..9f0b2ad8
--- /dev/null
+++ b/src/main/native/com/code_intelligence/jazzer/android/native_agent.cpp
@@ -0,0 +1,313 @@
+// Copyright 2023 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 <dlfcn.h>
+#include <jni.h>
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "absl/strings/str_split.h"
+#include "dex_file_manager.h"
+#include "jazzer_jvmti_allocator.h"
+#include "jvmti.h"
+#include "slicer/arrayview.h"
+#include "slicer/dex_format.h"
+#include "slicer/reader.h"
+#include "slicer/writer.h"
+
+static std::string agentOptions;
+static DexFileManager dfm;
+
+const std::string kAndroidAgentClass =
+ "com/code_intelligence/jazzer/android/DexFileManager";
+
+void retransformLoadedClasses(jvmtiEnv* jvmti, JNIEnv* env) {
+ jint classCount = 0;
+ jclass* classes;
+
+ jvmti->GetLoadedClasses(&classCount, &classes);
+
+ std::vector<jclass> classesToRetransform;
+ for (int i = 0; i < classCount; i++) {
+ jboolean isModifiable = false;
+ jvmti->IsModifiableClass(classes[i], &isModifiable);
+
+ if ((bool)isModifiable) {
+ classesToRetransform.push_back(classes[i]);
+ }
+ }
+
+ jvmtiError errorNum = jvmti->RetransformClasses(classesToRetransform.size(),
+ &classesToRetransform[0]);
+ if (errorNum != JVMTI_ERROR_NONE) {
+ std::cerr << "Could not retransform classes. JVMTI error: " << errorNum
+ << std::endl;
+ exit(1);
+ }
+}
+
+std::vector<std::string> getDexFiles(std::string jarPath, JNIEnv* env) {
+ jclass jazzerClass = env->FindClass(kAndroidAgentClass.c_str());
+ if (jazzerClass == nullptr) {
+ std::cerr << kAndroidAgentClass << " could not be found" << std::endl;
+ exit(1);
+ }
+
+ const char* getDexFilesFunction = "getDexFilesForJar";
+ jmethodID getDexFilesForJar =
+ env->GetStaticMethodID(jazzerClass, getDexFilesFunction,
+ "(Ljava/lang/String;)[Ljava/lang/String;");
+ if (getDexFilesForJar == nullptr) {
+ std::cerr << getDexFilesFunction << " could not be found\n";
+ exit(1);
+ }
+
+ jstring jJarFile = env->NewStringUTF(jarPath.data());
+ jobjectArray dexFilesArray = (jobjectArray)env->CallStaticObjectMethod(
+ jazzerClass, getDexFilesForJar, jJarFile);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ exit(1);
+ }
+
+ int length = env->GetArrayLength(dexFilesArray);
+
+ std::vector<std::string> dexFilesResult;
+ for (int i = 0; i < length; i++) {
+ jstring dexFileJstring =
+ (jstring)env->GetObjectArrayElement(dexFilesArray, i);
+ const char* dexFileChars = env->GetStringUTFChars(dexFileJstring, NULL);
+ std::string dexFileString(dexFileChars);
+
+ env->ReleaseStringUTFChars(dexFileJstring, dexFileChars);
+ dexFilesResult.push_back(dexFileString);
+ }
+
+ return dexFilesResult;
+}
+
+void initializeBootclassOverrideJar(std::string jarPath, JNIEnv* env) {
+ std::vector<std::string> dexFiles = getDexFiles(jarPath, env);
+
+ std::cerr << "Adding DEX files for: " << jarPath << std::endl;
+ for (int i = 0; i < dexFiles.size(); i++) {
+ std::cerr << "DEX FILE: " << dexFiles[i] << std::endl;
+ }
+
+ for (int i = 0; i < dexFiles.size(); i++) {
+ jclass bootHelperClass = env->FindClass(kAndroidAgentClass.c_str());
+ if (bootHelperClass == nullptr) {
+ std::cerr << kAndroidAgentClass << " could not be found" << std::endl;
+ exit(1);
+ }
+
+ jmethodID getBytecodeFromDex =
+ env->GetStaticMethodID(bootHelperClass, "getBytecodeFromDex",
+ "(Ljava/lang/String;Ljava/lang/String;)[B");
+ if (getBytecodeFromDex == nullptr) {
+ std::cerr << "'getBytecodeFromDex' not found\n";
+ exit(1);
+ }
+
+ jstring jjarPath = env->NewStringUTF(jarPath.data());
+ jstring jdexFile = env->NewStringUTF(dexFiles[i].data());
+
+ int length = 1;
+ std::vector<unsigned char> dexFileBytes;
+
+ jbyteArray dexBytes = (jbyteArray)env->CallStaticObjectMethod(
+ bootHelperClass, getBytecodeFromDex, jjarPath, jdexFile);
+
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ exit(1);
+ }
+
+ jbyte* data = new jbyte;
+ data = env->GetByteArrayElements(dexBytes, 0);
+ length = env->GetArrayLength(dexBytes);
+
+ for (int j = 0; j < length; j++) {
+ dexFileBytes.push_back(data[j]);
+ }
+
+ env->DeleteLocalRef(dexBytes);
+ env->DeleteLocalRef(jjarPath);
+ env->DeleteLocalRef(jdexFile);
+ env->DeleteLocalRef(bootHelperClass);
+
+ unsigned char* usData = reinterpret_cast<unsigned char*>(&dexFileBytes[0]);
+ dfm.addDexFile(usData, length);
+ }
+}
+
+void JNICALL jazzerClassFileLoadHook(
+ jvmtiEnv* jvmti, JNIEnv* jni_env, jclass class_being_redefined,
+ jobject loader, const char* name, jobject protection_domain,
+ jint class_data_len, const unsigned char* class_data,
+ jint* new_class_data_len, unsigned char** new_class_data) {
+ // check if Jazzer class
+ const char* prefix = "com/code_intelligence/jazzer/";
+ if (strncmp(name, prefix, 29) == 0) {
+ return;
+ }
+
+ int indx = dfm.findDexFileForClass(name);
+ if (indx < 0) {
+ return;
+ }
+
+ size_t newSize;
+ unsigned char* newClassDataResult =
+ dfm.getClassBytes(name, indx, jvmti, &newSize);
+
+ dex::Reader oldReader(const_cast<unsigned char*>(class_data),
+ (size_t)class_data_len);
+ dex::Reader newReader(newClassDataResult, newSize);
+ if (dfm.structureMatches(&oldReader, &newReader, name)) {
+ std::cout << "REDEFINING WITH INSTRUMENTATION: " << name << std::endl;
+ *new_class_data = newClassDataResult;
+ *new_class_data_len = static_cast<jint>(newSize);
+ }
+}
+
+bool fileExists(std::string filePath) { return std::ifstream(filePath).good(); }
+
+void JNICALL jazzerVMInit(jvmtiEnv* jvmti_env, JNIEnv* jni_env,
+ jthread thread) {
+ // Parse agentOptions
+
+ std::stringstream ss(agentOptions);
+ std::string token;
+
+ std::string jazzerClassesJar;
+ std::vector<std::string> bootpathClassesOverrides;
+ while (std::getline(ss, token, ',')) {
+ std::vector<std::string> split =
+ absl::StrSplit(token, absl::MaxSplits('=', 1));
+ if (split.size() < 2) {
+ std::cerr << "ERROR: no option given for: " << token;
+ exit(1);
+ }
+
+ if (split[0] == "injectJars") {
+ jazzerClassesJar = split[1];
+ } else if (split[0] == "bootstrapClassOverrides") {
+ bootpathClassesOverrides =
+ absl::StrSplit(split[1], absl::MaxSplits(':', 10));
+ }
+ }
+
+ if (!fileExists(jazzerClassesJar)) {
+ std::cerr << "ERROR: Jazzer bootstrap class file not found at: "
+ << jazzerClassesJar << std::endl;
+ exit(1);
+ }
+
+ jvmti_env->AddToBootstrapClassLoaderSearch(jazzerClassesJar.c_str());
+
+ jvmtiCapabilities jazzerJvmtiCapabilities = {
+ .can_tag_objects = 0,
+ .can_generate_field_modification_events = 0,
+ .can_generate_field_access_events = 0,
+ .can_get_bytecodes = 0,
+ .can_get_synthetic_attribute = 0,
+ .can_get_owned_monitor_info = 0,
+ .can_get_current_contended_monitor = 0,
+ .can_get_monitor_info = 0,
+ .can_pop_frame = 0,
+ .can_redefine_classes = 1,
+ .can_signal_thread = 0,
+ .can_get_source_file_name = 1,
+ .can_get_line_numbers = 0,
+ .can_get_source_debug_extension = 0,
+ .can_access_local_variables = 0,
+ .can_maintain_original_method_order = 0,
+ .can_generate_single_step_events = 0,
+ .can_generate_exception_events = 0,
+ .can_generate_frame_pop_events = 0,
+ .can_generate_breakpoint_events = 0,
+ .can_suspend = 0,
+ .can_redefine_any_class = 0,
+ .can_get_current_thread_cpu_time = 0,
+ .can_get_thread_cpu_time = 0,
+ .can_generate_method_entry_events = 0,
+ .can_generate_method_exit_events = 0,
+ .can_generate_all_class_hook_events = 0,
+ .can_generate_compiled_method_load_events = 0,
+ .can_generate_monitor_events = 0,
+ .can_generate_vm_object_alloc_events = 0,
+ .can_generate_native_method_bind_events = 0,
+ .can_generate_garbage_collection_events = 0,
+ .can_generate_object_free_events = 0,
+ .can_force_early_return = 0,
+ .can_get_owned_monitor_stack_depth_info = 0,
+ .can_get_constant_pool = 0,
+ .can_set_native_method_prefix = 0,
+ .can_retransform_classes = 1,
+ .can_retransform_any_class = 0,
+ .can_generate_resource_exhaustion_heap_events = 0,
+ .can_generate_resource_exhaustion_threads_events = 0,
+ };
+
+ jvmtiError je = jvmti_env->AddCapabilities(&jazzerJvmtiCapabilities);
+ if (je != JVMTI_ERROR_NONE) {
+ std::cerr << "JVMTI ERROR: " << je << std::endl;
+ exit(1);
+ }
+
+ for (int i = 0; i < bootpathClassesOverrides.size(); i++) {
+ if (!fileExists(bootpathClassesOverrides[i])) {
+ std::cerr << "ERROR: Bootpath Class override jar not found at: "
+ << bootpathClassesOverrides[i] << std::endl;
+ exit(1);
+ }
+
+ initializeBootclassOverrideJar(bootpathClassesOverrides[i], jni_env);
+ }
+
+ retransformLoadedClasses(jvmti_env, jni_env);
+}
+
+JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
+ jvmtiEnv* jvmti = nullptr;
+ if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_2) != JNI_OK) {
+ return 1;
+ }
+
+ jvmtiEventCallbacks callbacks;
+
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.ClassFileLoadHook = jazzerClassFileLoadHook;
+ callbacks.VMInit = jazzerVMInit;
+
+ jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks));
+ jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, NULL);
+ jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
+
+ // Save the options string here, this is the only time it will be available
+ // however, we wont be able to use this to initialize until VMInit callback is
+ // called
+ agentOptions = std::string(options);
+ return 0;
+}