Skip to content

Commit

Permalink
[core] Update method references in jit info when hooking
Browse files Browse the repository at this point in the history
Ensure that the previous jit info are still visible on the reference path, prevents jit garbage collector from recycling jit info because our backup method still uses them. I think this will be better than the previous implementation (force clearing possible references to these jit info in backup method). This may also fixes a possible hook inactivation when hooked methods are re-compiled by JIT.
  • Loading branch information
canyie committed Dec 16, 2021
1 parent 16b5520 commit b33057e
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 38 deletions.
105 changes: 76 additions & 29 deletions core/src/main/cpp/android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ void (*Android::end_gc_critical_section)(void*) = nullptr;
void* Android::class_linker_ = nullptr;
void (*Android::make_visibly_initialized_)(void*, void*, bool) = nullptr;

void* Android::jit_code_cache_ = nullptr;
void (*Android::move_obsolete_method_)(void*, void*, void*) = nullptr;

void Android::Init(JNIEnv* env, int sdk_version, bool disable_hiddenapi_policy, bool disable_hiddenapi_policy_for_platform) {
Android::version = sdk_version;
if (UNLIKELY(env->GetJavaVM(&jvm) != JNI_OK)) {
Expand Down Expand Up @@ -82,9 +85,7 @@ void Android::Init(JNIEnv* env, int sdk_version, bool disable_hiddenapi_policy,
art::Jit::Init(&art_lib_handle, &jit_lib_handle);
}

if (UNLIKELY(sdk_version >= kR)) {
InitClassLinker(jvm, &art_lib_handle);
}
InitMembersFromRuntime(jvm, &art_lib_handle);
}

WellKnownClasses::Init(env);
Expand Down Expand Up @@ -135,7 +136,7 @@ else \
#pragma clang diagnostic pop

static bool FakeProcessProfilingInfo() {
LOGI("Skipped ProcessProfilingInfo/NotifyJitActivity.");
LOGI("Skipped ProcessProfilingInfo.");
return true;
}

Expand Down Expand Up @@ -168,48 +169,94 @@ bool Android::DisableProfileSaver() {
return true;
}

void Android::InitClassLinker(JavaVM* jvm, const ElfImg* handle) {
make_visibly_initialized_ = reinterpret_cast<void (*)(void*, void*, bool)>(handle->GetSymbolAddress(
"_ZN3art11ClassLinker40MakeInitializedClassesVisiblyInitializedEPNS_6ThreadEb"));
if (UNLIKELY(!make_visibly_initialized_)) {
LOGE("ClassLinker::MakeInitializedClassesVisiblyInitialized not found");
void Android::InitMembersFromRuntime(JavaVM* jvm, const ElfImg* handle) {
if (version < kQ) {
// ClassLinker is unnecessary before R.
// JIT was added in Android N but MoveObsoleteMethod was added in Android O
// and didn't find a stable way to retrieve jit code cache until Q
// from Runtime object, so try to retrieve from ProfileSaver.
// TODO: Still clearing jit info on Android N but only for jit-compiled methods.
if (version >= kO) {
InitJitCodeCache(nullptr, 0, handle);
}
return;
}

void** instance_ptr = static_cast<void**>(handle->GetSymbolAddress("_ZN3art7Runtime9instance_E"));
void* runtime;
if (UNLIKELY(!instance_ptr || !(runtime = *instance_ptr))) {
LOGE("Unable to get Runtime.");
LOGE("Unable to retrieve Runtime.");
return;
}

constexpr size_t kDifference = sizeof(std::unique_ptr<void>) + sizeof(void*) + sizeof(void*);
#ifdef __LP64__
constexpr size_t kDefaultClassLinkerOffset = 472;
#else
constexpr size_t kDefaultClassLinkerOffset = 276;
#endif
constexpr size_t kDefaultJavaVMOffset = kDefaultClassLinkerOffset + kDifference;

auto jvm_ptr = reinterpret_cast<std::unique_ptr<JavaVM>*>(reinterpret_cast<uintptr_t>(runtime) + kDefaultJavaVMOffset);
size_t class_linker_offset;
if (LIKELY(jvm_ptr->get() == jvm)) {
size_t jvm_offset = OffsetOfJavaVm();
auto val = jvm_offset
? reinterpret_cast<std::unique_ptr<JavaVM>*>(reinterpret_cast<uintptr_t>(runtime) + jvm_offset)->get()
: nullptr;
if (LIKELY(val == jvm)) {
LOGD("JavaVM offset matches the default offset");
class_linker_offset = kDefaultClassLinkerOffset;
} else {
LOGW("JavaVM offset mismatches the default offset, try search the memory of Runtime");
int jvm_offset = Memory::FindOffset(runtime, jvm, 1024, 4);
if (UNLIKELY(jvm_offset == -1)) {
int offset = Memory::FindOffset(runtime, jvm, 1024, 4);
if (UNLIKELY(offset == -1)) {
LOGE("Failed to find java vm from Runtime");
return;
}
class_linker_offset = jvm_offset - kDifference;
LOGW("New java_vm_offset: %d, class_linker_offset: %u", jvm_offset, class_linker_offset);
jvm_offset = offset;
LOGW("Found JavaVM in Runtime at %zu", jvm_offset);
}
void* class_linker = *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(runtime) + class_linker_offset);
InitClassLinker(runtime, jvm_offset, handle);
InitJitCodeCache(runtime, jvm_offset, handle);
}

void Android::InitClassLinker(void* runtime, size_t java_vm_offset, const ElfImg* handle) {
if (version < kR) return;
make_visibly_initialized_ = reinterpret_cast<void (*)(void*, void*, bool)>(handle->GetSymbolAddress(
"_ZN3art11ClassLinker40MakeInitializedClassesVisiblyInitializedEPNS_6ThreadEb"));
if (UNLIKELY(!make_visibly_initialized_)) {
LOGE("ClassLinker::MakeInitializedClassesVisiblyInitialized not found");
return;
}

constexpr size_t kDifference = sizeof(std::unique_ptr<void>) + sizeof(void*) * 2;

void* class_linker = *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(runtime) + java_vm_offset - kDifference);
SetClassLinker(class_linker);
}

void Android::InitJitCodeCache(void *runtime, size_t java_vm_offset, const ElfImg *handle) {
move_obsolete_method_ = reinterpret_cast<void (*)(void*, void*, void*)>(handle->GetSymbolAddress(
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_"));
if (UNLIKELY(!move_obsolete_method_)) {
LOGW("JitCodeCache::MoveObsoleteMethod not found. Fallback to clearing jit info.");
return;
}
if (UNLIKELY(!runtime)) {
// We are not safe to get jit code cache from Runtime... then try ProfileSaver.
// ProfileSaver is not available before app starts, so we first try Runtime.

// class ProfileSaver {
// static ProfileSaver* instance_;
// jit::JitCodeCache* jit_code_cache_;
// }
void*** symbol = reinterpret_cast<void***>(handle->GetSymbolAddress(
"_ZN3art12ProfileSaver9instance_E"));
if (UNLIKELY(symbol == nullptr)) {
LOGW("ProfileSaver::instance_ not found. Fallback to clearing jit info.");
return;
}
void** profile_saver = *symbol;
if (UNLIKELY(profile_saver == nullptr)) {
LOGW("ProfileSaver is not initialized, cannot get jit code cache. Fallback to clearing jit info.");
return;
}
if (UNLIKELY((jit_code_cache_ = *profile_saver) == nullptr)) {
LOGE("ProfileSaver is initialized but no jit code cache??? Fallback to clearing jit info.");
}
return;
}
constexpr size_t kDifference = sizeof(std::unique_ptr<void>) * 2;
jit_code_cache_ = *reinterpret_cast<void**>(reinterpret_cast<uintptr_t>(runtime) + java_vm_offset + kDifference);
}

ALWAYS_INLINE ScopedGCCriticalSection::ScopedGCCriticalSection(void* self, art::GcCause cause,
art::CollectorType collector) {
Android::StartGCCriticalSection(this, self, cause, collector);
Expand Down
29 changes: 27 additions & 2 deletions core/src/main/cpp/android.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace pine {

class Android final {
public:
static inline bool Is64Bit() {
static inline constexpr bool Is64Bit() {
return sizeof(void*) == 8;
}

Expand Down Expand Up @@ -51,6 +51,14 @@ namespace pine {
make_visibly_initialized_(class_linker_, thread, wait);
}

static bool MoveJitInfo(void* from, void* to) {
if (LIKELY(jit_code_cache_ && move_obsolete_method_)) {
move_obsolete_method_(jit_code_cache_, from, to);
return true;
}
return false;
}

static int version;
static JavaVM* jvm;

Expand Down Expand Up @@ -99,7 +107,21 @@ namespace pine {
static constexpr int kSL = 32;
private:
static void DisableHiddenApiPolicy(const ElfImg* handle, bool application, bool platform);
static void InitClassLinker(JavaVM* jvm, const ElfImg* handle);
static void InitMembersFromRuntime(JavaVM* jvm, const ElfImg* handle);
static void InitClassLinker(void* runtime, size_t java_vm_offset, const ElfImg* handle);
static void InitJitCodeCache(void* runtime, size_t java_vm_offset, const ElfImg* handle);

static size_t OffsetOfJavaVm() {
switch (version) {
case kSL:
case kS:
case kR:
case kQ:
return Is64Bit() ? 496 : 288;
default:
FATAL("Unexpected android version %d", version);
}
}

static void (*suspend_vm)();
static void (*resume_vm)();
Expand All @@ -110,6 +132,9 @@ namespace pine {

static void* class_linker_;
static void (*make_visibly_initialized_)(void*, void*, bool);

static void* jit_code_cache_;
static void (*move_obsolete_method_)(void*, void*, void*);
DISALLOW_IMPLICIT_CONSTRUCTORS(Android);
};

Expand Down
21 changes: 14 additions & 7 deletions core/src/main/cpp/art/art_method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,20 @@ void ArtMethod::BackupFrom(ArtMethod* source, void* entry, bool is_inline_hook,
access_flags &= ~AccessFlags::kConstructor;
SetAccessFlags(access_flags);

if (Android::version >= Android::kN && Android::version < Android::kS
&& !is_inline_hook && !is_native_or_proxy && art_quick_to_interpreter_bridge) {
// On Android N+, the method may compiled by JIT, and unknown problem occurs when calling
// the backup method if we use entry replacement mode. Just use the interpreter to execute.
// Possible reason: compiled code is recycled in JIT garbage collection.
// TODO: Only do this if the method is compiled by jit.
// JitCodeCache::WillExecuteJitCode() or ContainsPc() not working for me.
// JIT compilation was added in Android N. When we hook a method, we may change its entry point
// and garbage collector loses reference to the entry point of compiled code, so jit info
// about the target method will be recycled -- but our backup method still references these info
// and causing random crashes. So we need to do something:
// 1. If possible, update the method references in jit info to backup method, so collector can
// know these jit info are still reachable and won't recycle them.
// 2. If not possible, clear references to these info in the backup method to prevent possible UAF.
// possible references: entry_point_from_compiled_code_ (may references jit compiled code),
// and data_ (may be a profiling info).

bool clear_jit_info_ref = Android::version >= Android::kN && !Android::MoveJitInfo(source, this)
&& !is_inline_hook && !is_native_or_proxy && art_quick_to_interpreter_bridge;
if (UNLIKELY(clear_jit_info_ref)) {
// entry_point_from_compiled_code_ (may references jit compiled code)
SetEntryPointFromCompiledCode(art_quick_to_interpreter_bridge);

// For non-native and non-proxy methods, the entry_point_from_jni_ member is used to save
Expand Down
1 change: 1 addition & 0 deletions core/src/main/cpp/utils/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define PINE_LOG_H

#include <android/log.h>
#include <cstdlib>

#define LOG_TAG "Pine"

Expand Down

0 comments on commit b33057e

Please sign in to comment.