From 628a3ce5da22ec69318312117b208d5649ab1520 Mon Sep 17 00:00:00 2001 From: solohsu Date: Fri, 1 Mar 2019 23:50:31 +0800 Subject: [PATCH 1/3] Fix compat mode not working correctly along with black/white list mode --- .../java/com/elderdrivers/riru/xposed/entry/Router.java | 7 ++----- .../riru/xposed/proxy/yahfa/BlackWhiteListProxy.java | 8 ++++++-- .../elderdrivers/riru/xposed/proxy/yahfa/NormalProxy.java | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/Router.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/Router.java index 7d358fb5..5aa1d10c 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/Router.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/entry/Router.java @@ -2,19 +2,15 @@ package com.elderdrivers.riru.xposed.entry; import android.text.TextUtils; -import com.elderdrivers.riru.xposed.Main; import com.elderdrivers.riru.xposed.core.HookMain; +import com.elderdrivers.riru.xposed.dexmaker.DynamicBridge; import com.elderdrivers.riru.xposed.entry.bootstrap.AppBootstrapHookInfo; import com.elderdrivers.riru.xposed.entry.bootstrap.SysBootstrapHookInfo; import com.elderdrivers.riru.xposed.entry.bootstrap.SysInnerHookInfo; import com.elderdrivers.riru.xposed.entry.hooker.SystemMainHooker; -import com.elderdrivers.riru.xposed.util.InlinedMethodCallers; import com.elderdrivers.riru.xposed.util.Utils; -import java.util.Arrays; - import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; public class Router { @@ -87,5 +83,6 @@ public class Router { public static void onEnterChildProcess() { forkCompleted = true; + DynamicBridge.onForkPost(); } } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/BlackWhiteListProxy.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/BlackWhiteListProxy.java index d361b38a..63a73723 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/BlackWhiteListProxy.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/BlackWhiteListProxy.java @@ -18,6 +18,8 @@ public class BlackWhiteListProxy { // when isDynamicModulesMode is not on final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); ConfigManager.setDynamicModulesMode(isDynamicModulesMode); + // call this to ensure the flag is set to false ASAP + Router.prepare(false); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote if (!isDynamicModulesMode) { Router.loadModulesSafely(); @@ -34,7 +36,7 @@ public class BlackWhiteListProxy { } Main.appDataDir = appDataDir; ConfigManager.setDynamicModulesMode(isDynamicModulesMode); - Router.prepare(false); + Router.onEnterChildProcess(); Router.installBootstrapHooks(false); Router.loadModulesSafely(); } @@ -46,6 +48,8 @@ public class BlackWhiteListProxy { final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); ConfigManager.setDynamicModulesMode(isDynamicModulesMode); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for main zygote + // set startsSystemServer flag used when loadModules + Router.prepare(true); // we never install bootstrap hooks here in black/white list mode // because installed hooks would be propagated to all child processes of main zygote // hence we cannot install hooks for processes like com.android.phone process who are @@ -65,7 +69,7 @@ public class BlackWhiteListProxy { } Main.appDataDir = getDataPathPrefix() + "android"; ConfigManager.setDynamicModulesMode(isDynamicModulesMode); - Router.prepare(true); + Router.onEnterChildProcess(); Router.installBootstrapHooks(true); Router.loadModulesSafely(); } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/NormalProxy.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/NormalProxy.java index 20988054..7303c4ce 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/NormalProxy.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/proxy/yahfa/NormalProxy.java @@ -2,7 +2,6 @@ package com.elderdrivers.riru.xposed.proxy.yahfa; import com.elderdrivers.riru.xposed.Main; import com.elderdrivers.riru.xposed.config.ConfigManager; -import com.elderdrivers.riru.xposed.dexmaker.DynamicBridge; import com.elderdrivers.riru.xposed.entry.Router; import com.elderdrivers.riru.xposed.util.PrebuiltMethodsDeopter; @@ -19,6 +18,7 @@ public class NormalProxy { Main.appDataDir = appDataDir; ConfigManager.setDynamicModulesMode(isDynamicModulesMode); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote + // call this to ensure the flag is set to false ASAP Router.prepare(false); // install bootstrap hooks for secondary zygote Router.installBootstrapHooks(false); @@ -31,7 +31,6 @@ public class NormalProxy { // TODO consider processes without forkAndSpecializePost called Main.reopenFilesAfterForkNative(); Router.onEnterChildProcess(); - DynamicBridge.onForkPost(); // load modules for each app process on its forked if dynamic modules mode is on Router.loadModulesSafely(); } @@ -41,6 +40,7 @@ public class NormalProxy { final boolean isDynamicModulesMode = Main.isDynamicModulesEnabled(); Main.appDataDir = getDataPathPrefix() + "android"; ConfigManager.setDynamicModulesMode(isDynamicModulesMode); + // set startsSystemServer flag used when loadModules Router.prepare(true); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for main zygote // install bootstrap hooks for main zygote as early as possible From 03ec9fa13fb45cbb9ea2a7f99f7c231c748ba543 Mon Sep 17 00:00:00 2001 From: solohsu Date: Sat, 2 Mar 2019 01:07:42 +0800 Subject: [PATCH 2/3] Reduce unnecessary IO when using compat mode --- .../riru/xposed/config/ConfigManager.java | 19 ++++-- .../riru/xposed/dexmaker/DexMakerUtils.java | 5 +- .../riru/xposed/dexmaker/DynamicBridge.java | 60 ++++++++++++------- .../riru/xposed/dexmaker/HookerDexMaker.java | 6 +- .../riru/xposed/util/FileUtils.java | 10 +++- .../riru/xposed/util/ProcessUtils.java | 4 ++ 6 files changed, 68 insertions(+), 36 deletions(-) diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java index 13446f75..784cdfb1 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java @@ -1,6 +1,7 @@ package com.elderdrivers.riru.xposed.config; import java.util.Collections; +import java.util.HashMap; import java.util.Set; import de.robv.android.xposed.SELinuxHelper; @@ -16,24 +17,32 @@ public class ConfigManager { private static final String USE_WHITE_LIST = INSTALLER_DATA_BASE_DIR + "conf/usewhitelist"; private static final String DYNAMIC_MODULES = INSTALLER_DATA_BASE_DIR + "conf/dynamicmodules"; private static final Set WHITE_LIST = Collections.singleton(INSTALLER_PACKAGE_NAME); + private static final HashMap compatModeCache = new HashMap<>(); private static volatile boolean IS_DYNAMIC_MODULES = false; + public static boolean isDynamicModulesMode() { + return IS_DYNAMIC_MODULES; + } + public static synchronized void setDynamicModulesMode(boolean isDynamicModulesMode) { if (isDynamicModulesMode != IS_DYNAMIC_MODULES) { IS_DYNAMIC_MODULES = isDynamicModulesMode; } } - public static boolean isDynamicModulesMode() { - return IS_DYNAMIC_MODULES; - } - public static boolean shouldUseWhitelist() { return isFileExists(USE_WHITE_LIST); } public static boolean shouldUseCompatMode(String packageName) { - return isFileExists(COMPAT_LIST_PATH + packageName); + Boolean result; + if (compatModeCache.containsKey(packageName) + && (result = compatModeCache.get(packageName)) != null) { + return result; + } + result = isFileExists(COMPAT_LIST_PATH + packageName); + compatModeCache.put(packageName, result); + return result; } public static boolean shouldHook(String packageName) { diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java index 671561f0..47eebdcd 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java @@ -10,7 +10,6 @@ import com.elderdrivers.riru.xposed.config.ConfigManager; import java.util.HashMap; import java.util.Map; -import de.robv.android.xposed.SELinuxHelper; import external.com.android.dx.Code; import external.com.android.dx.Local; import external.com.android.dx.TypeId; @@ -156,8 +155,8 @@ public class DexMakerUtils { code.loadConstant(booleanLocal, false); code.loadConstant(byteLocal, (byte) 0); code.loadConstant(charLocal, '\0'); - code.loadConstant(doubleLocal,0.0); - code.loadConstant(floatLocal,0.0f); + code.loadConstant(doubleLocal, 0.0); + code.loadConstant(floatLocal, 0.0f); code.loadConstant(intLocal, 0); code.loadConstant(longLocal, 0L); code.loadConstant(shortLocal, (short) 0); diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java index 4face33d..f9aca719 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java @@ -27,6 +27,10 @@ public final class DynamicBridge { private static File dexDir; private static File dexOptDir; + /** + * Reset dexPathInited flag once we enter child process + * since it might have been set to true in zygote process + */ public static void onForkPost() { dexPathInited.set(false); } @@ -46,36 +50,48 @@ public final class DynamicBridge { try { // for Android Oreo and later use InMemoryClassLoader if (!shouldUseInMemoryHook()) { - // using file based DexClassLoader - if (dexPathInited.compareAndSet(false, true)) { - // delete previous compiled dex to prevent potential crashing - // TODO find a way to reuse them in consideration of performance - try { - // we always choose to use device encrypted storage data on android N and later - // in case some app is installing hooks before phone is unlocked - String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/"; - dexDir = new File(fixedAppDataDir, "/cache/edhookers/" - + getCurrentProcessName().replace(":", "_") + "/"); - dexOptDir = new File(dexDir, "oat"); - dexDir.mkdirs(); - DexLog.d(Main.appProcessName + " deleting dir: " + dexOptDir.getAbsolutePath()); - try { - FileUtils.delete(dexOptDir); - } catch (Throwable throwable) { - } - } catch (Throwable throwable) { - DexLog.e("error when init dex path", throwable); - } - } + setupDexCachePath(); } dexMaker.start(hookMethod, additionalHookInfo, - hookMethod.getDeclaringClass().getClassLoader(), dexDir == null ? null : dexDir.getAbsolutePath()); + hookMethod.getDeclaringClass().getClassLoader(), getDexDirPath()); hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod()); } catch (Exception e) { DexLog.e("error occur when generating dex. dexDir=" + dexDir, e); } } + private static String getDexDirPath() { + if (dexDir == null) { + return null; + } + return dexDir.getAbsolutePath(); + } + + private static void setupDexCachePath() { + // using file based DexClassLoader + if (!dexPathInited.compareAndSet(false, true)) { + return; + } + // delete previous compiled dex to prevent potential crashing + // TODO find a way to reuse them in consideration of performance + try { + // we always choose to use device encrypted storage data on android N and later + // in case some app is installing hooks before phone is unlocked + String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/"; + dexDir = new File(fixedAppDataDir, "/cache/edhookers/" + + getCurrentProcessName().replace(":", "_") + "/"); + dexOptDir = new File(dexDir, "oat"); + dexDir.mkdirs(); + DexLog.d(Main.appProcessName + " deleting dir: " + dexOptDir.getAbsolutePath()); + try { + FileUtils.delete(dexOptDir); + } catch (Throwable throwable) { + } + } catch (Throwable throwable) { + DexLog.e("error when init dex path", throwable); + } + } + private static boolean checkMember(Member member) { if (member instanceof Method) { diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java index fdaf8146..163ca26a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java @@ -30,7 +30,6 @@ import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoBoxIfNeces import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoUnboxIfNecessary; import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.createResultLocals; import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.getObjTypeIdIfPrimitive; -import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.shouldUseInMemoryHook; public class HookerDexMaker { @@ -193,14 +192,11 @@ public class HookerDexMaker { generateCallBackupMethod(); ClassLoader loader; - if (shouldUseInMemoryHook()) { + if (TextUtils.isEmpty(mDexDirPath)) { // in memory dex classloader byte[] dexBytes = mDexMaker.generate(); loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader); } else { - if (TextUtils.isEmpty(mDexDirPath)) { - throw new IllegalArgumentException("dexDirPath should not be empty!!!"); - } // Create the dex file and load it. loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath)); } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java index dc34d07d..4443380a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java @@ -1,6 +1,8 @@ package com.elderdrivers.riru.xposed.util; +import android.annotation.SuppressLint; import android.os.Build; +import android.os.Process; import android.text.TextUtils; import java.io.BufferedReader; @@ -10,6 +12,8 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import static com.elderdrivers.riru.xposed.util.ProcessUtils.PER_USER_RANGE; + public class FileUtils { public static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; @@ -71,7 +75,11 @@ public class FileUtils { return dataDir.substring(lastIndex + 1); } + // FIXME: Although multi-users is considered here, but compat mode doesn't support other users' apps on Oreo and later yet. + @SuppressLint("SdCardPath") public static String getDataPathPrefix() { - return IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data/"; + int userId = Process.myUid() / PER_USER_RANGE; + String format = IS_USING_PROTECTED_STORAGE ? "/data/user_de/%d/" : "/data/user/%d/"; + return String.format(format, userId); } } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java index f5442ec8..3021b38f 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java @@ -12,6 +12,10 @@ import java.io.InputStreamReader; public class ProcessUtils { + // Copied from UserHandle, indicates range of uids allocated for a user. + public static final int PER_USER_RANGE = 100000; + public static final int USER_SYSTEM = 0; + public static String getCurrentProcessName() { String prettyName = Main.appProcessName; if (!TextUtils.isEmpty(prettyName)) { From ff595542c4c1baca70ac5f17f1d6ff0f03934858 Mon Sep 17 00:00:00 2001 From: solohsu Date: Sat, 2 Mar 2019 02:47:58 +0800 Subject: [PATCH 3/3] Add a Riru module api "shouldSkipUid" to skip uid filtering in riru-core --- Core/jni/main/main.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Core/jni/main/main.cpp b/Core/jni/main/main.cpp index 58337bfc..d8a43b01 100644 --- a/Core/jni/main/main.cpp +++ b/Core/jni/main/main.cpp @@ -19,6 +19,16 @@ #include "include/config.h" extern "C" { + +__attribute__((visibility("default"))) void onModuleLoaded() { + LOGI("onModuleLoaded: welcome to EdXposed!"); + install_inline_hooks(); +} + +__attribute__((visibility("default"))) int shouldSkipUid(int uid) { + return 0; +} + __attribute__((visibility("default"))) void nativeForkAndSpecializePre(JNIEnv *env, jclass clazz, jint _uid, jint gid, jintArray gids, @@ -42,11 +52,6 @@ __attribute__((visibility("default"))) int nativeForkAndSpecializePost(JNIEnv *e return onNativeForkAndSpecializePost(env, clazz, res); } -__attribute__((visibility("default"))) void onModuleLoaded() { - LOGI("onModuleLoaded: welcome to EdXposed!"); - install_inline_hooks(); -} - __attribute__((visibility("default"))) void nativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, @@ -55,7 +60,6 @@ void nativeForkSystemServerPre(JNIEnv *env, jclass clazz, uid_t uid, gid_t gid, permittedCapabilities, effectiveCapabilities); } - __attribute__((visibility("default"))) int nativeForkSystemServerPost(JNIEnv *env, jclass clazz, jint res) { return onNativeForkSystemServerPost(env, clazz, res);