Fix crash when hooked static method invokes class initialization

This commit is contained in:
solohsu 2019-06-20 20:37:29 +08:00
parent 1b45e8f79e
commit ee2a3f5d02
17 changed files with 186 additions and 28 deletions

View File

@ -1,4 +1,4 @@
version: '0.4.4.7_alpha({build})'
version: '0.4.5.0_alpha({build})'
environment:
ANDROID_HOME: C:\android-sdk-windows

View File

@ -1,8 +1,10 @@
package com.elderdrivers.riru.edxp.art;
import com.elderdrivers.riru.common.KeepAll;
import com.elderdrivers.riru.edxp.core.Yahfa;
import java.lang.reflect.Member;
import java.util.function.Consumer;
import de.robv.android.xposed.PendingHooks;
@ -10,7 +12,13 @@ public class ClassLinker implements KeepAll {
public static native void setEntryPointsToInterpreter(Member method);
public static void onPreFixupStaticTrampolines(Class clazz) {
// remove modified native flags to let FixupStaticTrampolines fill in right entrypoints
PendingHooks.removeNativeFlags(clazz);
}
public static void onPostFixupStaticTrampolines(Class clazz) {
// native flags will be re-set in hooking logic
PendingHooks.hookPendingMethod(clazz);
}
}

View File

@ -1,5 +1,6 @@
package com.elderdrivers.riru.edxp.config;
import com.elderdrivers.riru.edxp.core.Yahfa;
import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter;
import com.elderdrivers.riru.edxp.hook.HookProvider;
@ -37,4 +38,9 @@ public abstract class BaseHookProvider implements HookProvider {
public boolean initXResourcesNative() {
return false;
}
@Override
public void setNativeFlag(Member hookMethod, boolean isNative) {
Yahfa.setNativeFlag(hookMethod, isNative);
}
}

View File

@ -15,4 +15,6 @@ public class Yahfa {
public static native void init(int SDK_version);
public static native void setMethodNonCompilable(Member member);
public static native boolean setNativeFlag(Member member, boolean isNative);
}

View File

@ -29,14 +29,8 @@ public class NormalProxy extends BaseProxy {
}
public void forkAndSpecializePost(int pid, String appDataDir, String niceName) {
// TODO consider processes without forkAndSpecializePost called
ConfigManager.appDataDir = appDataDir;
ConfigManager.niceName = niceName;
mRouter.prepare(false);
mRouter.onEnterChildProcess();
// load modules for each app process on its forked if dynamic modules mode is on
mRouter.loadModulesSafely(false, true);
mRouter.onForkFinish();
// TODO consider processes without forkAndSpecializePost being called
forkPostCommon(pid, false, appDataDir, niceName);
}
public void forkSystemServerPre(int uid, int gid, int[] gids, int debugFlags, int[][] rlimits,
@ -58,12 +52,20 @@ public class NormalProxy extends BaseProxy {
public void forkSystemServerPost(int pid) {
// in system_server process
ConfigManager.appDataDir = getDataPathPrefix() + "android";
ConfigManager.niceName = "system_server";
mRouter.prepare(true);
forkPostCommon(pid, true,
getDataPathPrefix() + "android", "system_server");
}
private void forkPostCommon(int pid, boolean isSystem, String appDataDir, String niceName) {
ConfigManager.appDataDir = appDataDir;
ConfigManager.niceName = niceName;
mRouter.prepare(isSystem);
mRouter.onEnterChildProcess();
// reload module list if dynamic mode is on
if (ConfigManager.isDynamicModulesEnabled()) {
// FIXME this could be error-prone because hooks installed inside old versions
// of initZygote instances of same module are not unhooked
mRouter.loadModulesSafely(false, true);
}
mRouter.onForkFinish();

View File

@ -2,6 +2,7 @@ package com.elderdrivers.riru.edxp.util;
import android.os.Build;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
@ -37,7 +38,7 @@ public class ClassUtils {
}
public static boolean shouldDelayHook(Member hookMethod) {
if (hookMethod == null) {
if (hookMethod == null || hookMethod instanceof Constructor) {
return false;
}
Class declaringClass = hookMethod.getDeclaringClass();

View File

@ -4,7 +4,7 @@ import org.gradle.internal.os.OperatingSystem
apply plugin: 'com.android.library'
// Values set here will be overriden by AppVeyor, feel free to modify during development.
def buildVersionName = 'v0.4.4.7_alpha'
def buildVersionName = 'v0.4.5.0_alpha'
def buildVersionCode = 10000
if (System.env.APPVEYOR_BUILD_VERSION != null) {
@ -165,7 +165,7 @@ afterEvaluate {
into "${zipPathMagiskRelease}/common"
filter { line -> line
.replaceAll('%VERSION%', "$version")
.replaceAll('%VERSION_CODE%', "$versionCode")
.replaceAll('%VERSION_CODE%', "$versionCode")
.replaceAll('%BACKEND%', "$backendCapped") }
}
copy {

View File

@ -23,6 +23,8 @@ void Java_lab_galaxy_yahfa_HookMain_ensureMethodCached(JNIEnv *env, jclass clazz
void setNonCompilable(void *method);
bool setNativeFlag(void *method, bool isNative);
static void *getResolvedMethodsAddr(JNIEnv *, jobject);
#ifdef __cplusplus

View File

@ -2,6 +2,7 @@
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdbool.h>
#include "common.h"
#include "env.h"
@ -128,6 +129,26 @@ void setNonCompilable(void *method) {
);
}
bool setNativeFlag(void *method, bool isNative) {
int access_flags = read32((char *) method + OFFSET_access_flags_in_ArtMethod);
LOGI("setNativeFlag: access flags is 0x%x", access_flags);
int old_access_flags = access_flags;
if (isNative) {
access_flags |= kAccNative;
} else {
access_flags &= ~kAccNative;
}
if (access_flags != old_access_flags) {
memcpy(
(char *) method + OFFSET_access_flags_in_ArtMethod,
&access_flags,
4
);
return true;
}
return false;
}
static int doBackupAndHook(JNIEnv *env, void *targetMethod, void *hookMethod, void *backupMethod) {
if (hookCount >= hookCap) {
LOGI("not enough capacity. Allocating...");
@ -182,13 +203,7 @@ static int doBackupAndHook(JNIEnv *env, void *targetMethod, void *hookMethod, vo
// set the target method to native so that Android O wouldn't invoke it with interpreter
if (SDKVersion >= ANDROID_O) {
int access_flags = read32((char *) targetMethod + OFFSET_access_flags_in_ArtMethod);
access_flags |= kAccNative;
memcpy(
(char *) targetMethod + OFFSET_access_flags_in_ArtMethod,
&access_flags,
4
);
setNativeFlag(targetMethod, true);
LOGI("access flags is 0x%x", access_flags);
}

View File

@ -3,9 +3,12 @@
#include <JNIHelper.h>
#include <base/object.h>
#include <art/runtime/mirror/class.h>
#include <android-base/strings.h>
#include "runtime.h"
#include "jni_env_ext.h"
#include "edxp_context.h"
#include "jni/edxp_pending_hooks.h"
namespace art {
@ -28,8 +31,17 @@ namespace art {
}
CREATE_HOOK_STUB_ENTRIES(void, FixupStaticTrampolines, void *thiz, void *clazz_ptr) {
art::mirror::Class clazz(clazz_ptr);
std::string storage;
const char *desc = clazz.GetDescriptor(&storage);
bool should_intercept = edxp::IsClassPending(desc);
if (UNLIKELY(should_intercept)) {
edxp::Context::GetInstance()->CallOnPreFixupStaticTrampolines(clazz_ptr);
}
FixupStaticTrampolinesBackup(thiz, clazz_ptr);
edxp::Context::GetInstance()->CallOnPostFixupStaticTrampolines(clazz_ptr);
if (UNLIKELY(should_intercept)) {
edxp::Context::GetInstance()->CallOnPostFixupStaticTrampolines(clazz_ptr);
}
}
public:

View File

@ -14,6 +14,7 @@
#include <art/runtime/mirror/class.h>
#include <android-base/strings.h>
#include <nativehelper/scoped_local_ref.h>
#include <jni/edxp_pending_hooks.h>
#include "edxp_context.h"
#include "config_manager.h"
@ -43,8 +44,8 @@ namespace edxp {
return inject_class_loader_;
}
void Context::CallOnPostFixupStaticTrampolines(void *class_ptr) {
if (UNLIKELY(!post_fixup_static_mid_ || !class_linker_class_)) {
void Context::CallPostFixupStaticTrampolinesCallback(void *class_ptr, jmethodID callback_mid) {
if (UNLIKELY(!callback_mid || !class_linker_class_)) {
return;
}
if (!class_ptr) {
@ -55,10 +56,18 @@ namespace edxp {
art::JNIEnvExt env_ext(env);
ScopedLocalRef clazz(env, env_ext.NewLocalRefer(class_ptr));
if (clazz != nullptr) {
JNI_CallStaticVoidMethod(env, class_linker_class_, post_fixup_static_mid_, clazz.get());
JNI_CallStaticVoidMethod(env, class_linker_class_, callback_mid, clazz.get());
}
}
void Context::CallOnPreFixupStaticTrampolines(void *class_ptr) {
CallPostFixupStaticTrampolinesCallback(class_ptr, pre_fixup_static_mid_);
}
void Context::CallOnPostFixupStaticTrampolines(void *class_ptr) {
CallPostFixupStaticTrampolinesCallback(class_ptr, post_fixup_static_mid_);
}
void Context::LoadDexAndInit(JNIEnv *env, const char *dex_path) {
if (LIKELY(initialized_)) {
return;
@ -97,6 +106,9 @@ namespace edxp {
env->GetJavaVM(&vm_);
class_linker_class_ = (jclass) env->NewGlobalRef(
FindClassFromLoader(env, kClassLinkerClassName));
pre_fixup_static_mid_ = JNI_GetStaticMethodID(env, class_linker_class_,
"onPreFixupStaticTrampolines",
"(Ljava/lang/Class;)V");
post_fixup_static_mid_ = JNI_GetStaticMethodID(env, class_linker_class_,
"onPostFixupStaticTrampolines",
"(Ljava/lang/Class;)V");
@ -110,6 +122,7 @@ namespace edxp {
RegisterArtClassLinker(env);
RegisterArtHeap(env);
RegisterEdxpYahfa(env);
RegisterPendingHooks(env);
// must call entry class's methods after all native methods registered
if (LIKELY(entry_class_)) {

View File

@ -22,6 +22,8 @@ namespace edxp {
jobject GetCurrentClassLoader() const;
void CallOnPreFixupStaticTrampolines(void *class_ptr);
void CallOnPostFixupStaticTrampolines(void *class_ptr);
void PrepareJavaEnv(JNIEnv *env);
@ -70,6 +72,7 @@ namespace edxp {
jstring nice_name_ = nullptr;
JavaVM *vm_ = nullptr;
jclass class_linker_class_ = nullptr;
jmethodID pre_fixup_static_mid_ = nullptr;
jmethodID post_fixup_static_mid_ = nullptr;
Context() {}
@ -79,6 +82,8 @@ namespace edxp {
void LoadDexAndInit(JNIEnv *env, const char *dex_path);
jclass FindClassFromLoader(JNIEnv *env, jobject class_loader, const char *class_name) const;
void CallPostFixupStaticTrampolinesCallback(void *class_ptr, jmethodID mid);
};
}

View File

@ -0,0 +1,30 @@
#include <nativehelper/jni_macros.h>
#include <set>
#include <string>
#include "jni.h"
#include "native_util.h"
#include "edxp_pending_hooks.h"
namespace edxp {
static std::set<std::string> class_descs_;
bool IsClassPending(const char *class_desc) {
return class_descs_.find(class_desc) != class_descs_.end();
}
static void PendingHooks_recordPendingMethodNative(JNI_START, jstring class_desc) {
const char *class_desc_chars = env->GetStringUTFChars(class_desc, JNI_FALSE);
class_descs_.insert(class_desc_chars);
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(PendingHooks, recordPendingMethodNative, "(Ljava/lang/String;)V"),
};
void RegisterPendingHooks(JNIEnv *env) {
REGISTER_EDXP_NATIVE_METHODS("de.robv.android.xposed.PendingHooks");
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include "jni.h"
namespace edxp {
bool IsClassPending(const char *);
void RegisterPendingHooks(JNIEnv *);
} // namespace edxp

View File

@ -39,6 +39,19 @@ namespace edxp {
setNonCompilable(art_method);
}
static jboolean Yahfa_setNativeFlag(JNI_START, jobject member, jboolean is_native) {
if (!member) {
LOGE("setNativeFlagNative: member is null");
return JNI_FALSE;
}
void *art_method = env->FromReflectedMethod(member);
if (!art_method) {
LOGE("setNativeFlagNative: art_method is null");
return JNI_FALSE;
}
return (jboolean) setNativeFlag(art_method, is_native);
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Yahfa, init, "(I)V"),
NATIVE_METHOD(Yahfa, findMethodNative,
@ -48,6 +61,7 @@ namespace edxp {
NATIVE_METHOD(Yahfa, ensureMethodCached,
"(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V"),
NATIVE_METHOD(Yahfa, setMethodNonCompilable, "(Ljava/lang/reflect/Member;)V"),
NATIVE_METHOD(Yahfa, setNativeFlag, "(Ljava/lang/reflect/Member;Z)Z"),
};
void RegisterEdxpYahfa(JNIEnv *env) {

View File

@ -25,4 +25,7 @@ public interface HookProvider {
boolean initXResourcesNative();
boolean removeFinalFlagNative(Class clazz);
void setNativeFlag(Member hookMethod, boolean isNative);
}

View File

@ -1,14 +1,21 @@
package de.robv.android.xposed;
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
public final class PendingHooks {
// GuardedBy("PendingHooks.class")
private static final ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>
sPendingHookMethods = new ConcurrentHashMap<>();
// GuardedBy("PendingHooks.class")
private static final HashSet<Member> sNonNativeMethods = new HashSet<>();
public synchronized static void hookPendingMethod(Class clazz) {
for (Member member : sPendingHookMethods.keySet()) {
@ -18,7 +25,33 @@ public final class PendingHooks {
}
}
public synchronized static void recordPendingMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalInfo) {
sPendingHookMethods.put(hookMethod, additionalInfo);
public synchronized static void removeNativeFlags(Class clazz) {
for (Member member : sPendingHookMethods.keySet()) {
if (member.getDeclaringClass().equals(clazz) && sNonNativeMethods.contains(member)) {
EdXpConfigGlobal.getHookProvider().setNativeFlag(member, false);
}
}
}
public synchronized static void recordPendingMethod(Member hookMethod,
XposedBridge.AdditionalHookInfo additionalInfo) {
if (!Modifier.isNative(hookMethod.getModifiers())) {
// record non-native methods for later native flag temporary removing
sNonNativeMethods.add(hookMethod);
}
EdXpConfigGlobal.getHookProvider().setNativeFlag(hookMethod, true);
sPendingHookMethods.put(hookMethod, additionalInfo);
recordPendingMethodNative("L" +
hookMethod.getDeclaringClass().getName().replace(".", "/") + ";");
}
public synchronized void cleanUp() {
sPendingHookMethods.clear();
// sNonNativeMethods should be cleared very carefully because their
// pre-set native flag have to be removed if its hooking is cancelled
// before its class is initialized
// sNonNativeMethods.clear();
}
private static native void recordPendingMethodNative(String classDesc);
}