Fix module loading (#2418)

This commit is contained in:
LoveSy 2023-03-05 20:37:21 +08:00 committed by GitHub
parent 55afed974d
commit 516a4aa99d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 98 additions and 69 deletions

View File

@ -75,10 +75,10 @@ public abstract class XC_MethodHook extends XCallback {
* @param param Information about the method call. * @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { protected void beforeHookedMethod(MethodHookParam<?> param) throws Throwable {
} }
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable { public void callBeforeHookedMethod(MethodHookParam<?> param) throws Throwable {
beforeHookedMethod(param); beforeHookedMethod(param);
} }
@ -93,10 +93,10 @@ public abstract class XC_MethodHook extends XCallback {
* @param param Information about the method call. * @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged. * @throws Throwable Everything the callback throws is caught and logged.
*/ */
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
} }
public void callAfterHookedMethod(MethodHookParam param) throws Throwable { public void callAfterHookedMethod(MethodHookParam<?> param) throws Throwable {
afterHookedMethod(param); afterHookedMethod(param);
} }

View File

@ -50,6 +50,7 @@ import java.security.MessageDigest;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import de.robv.android.xposed.services.FileResult; import de.robv.android.xposed.services.FileResult;
@ -160,29 +161,27 @@ public final class XSharedPreferences implements SharedPreferences {
*/ */
public XSharedPreferences(String packageName, String prefFileName) { public XSharedPreferences(String packageName, String prefFileName) {
boolean newModule = false; boolean newModule = false;
Set<String> modules = XposedInit.getLoadedModules(); var m = XposedInit.getLoadedModules().getOrDefault(packageName, Optional.empty());
for (String m : modules) { if (m.isPresent()) {
if (m.contains("/" + packageName + "-")) { boolean isModule = false;
boolean isModule = false; int xposedminversion = -1;
int xposedminversion = -1; boolean xposedsharedprefs = false;
boolean xposedsharedprefs = false; try {
try { Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m.get()));
Map<String, Object> metaData = MetaDataReader.getMetaData(new File(m)); isModule = metaData.containsKey("xposedminversion");
isModule = metaData.containsKey("xposedminversion"); if (isModule) {
if (isModule) { Object minVersionRaw = metaData.get("xposedminversion");
Object minVersionRaw = metaData.get("xposedminversion"); if (minVersionRaw instanceof Integer) {
if (minVersionRaw instanceof Integer) { xposedminversion = (Integer) minVersionRaw;
xposedminversion = (Integer) minVersionRaw; } else if (minVersionRaw instanceof String) {
} else if (minVersionRaw instanceof String) { xposedminversion = MetaDataReader.extractIntPart((String) minVersionRaw);
xposedminversion = MetaDataReader.extractIntPart((String) minVersionRaw);
}
xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
} }
} catch (NumberFormatException | IOException e) { xposedsharedprefs = metaData.containsKey("xposedsharedprefs");
Log.w(TAG, "Apk parser fails: " + e);
} }
newModule = isModule && (xposedminversion > 92 || xposedsharedprefs); } catch (NumberFormatException | IOException e) {
Log.w(TAG, "Apk parser fails: " + e);
} }
newModule = isModule && (xposedminversion > 92 || xposedsharedprefs);
} }
if (newModule) { if (newModule) {
mFile = new File(serviceClient.getPrefsPath(packageName), prefFileName + ".xml"); mFile = new File(serviceClient.getPrefsPath(packageName), prefFileName + ".xml");

View File

@ -29,6 +29,7 @@ import static de.robv.android.xposed.XposedHelpers.getObjectField;
import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType; import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
import static de.robv.android.xposed.XposedHelpers.setStaticObjectField; import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
import android.app.ActivityThread;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.ResourcesImpl; import android.content.res.ResourcesImpl;
@ -50,9 +51,10 @@ import java.lang.ref.WeakReference;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_InitPackageResources;
@ -82,7 +84,7 @@ public final class XposedInit {
findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication", findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication",
ApplicationInfo.class, new XC_MethodHook() { ApplicationInfo.class, new XC_MethodHook() {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) { protected void beforeHookedMethod(MethodHookParam<?> param) {
ApplicationInfo app = (ApplicationInfo) param.args[0]; ApplicationInfo app = (ApplicationInfo) param.args[0];
XResources.setPackageNameForResDir(app.packageName, XResources.setPackageNameForResDir(app.packageName,
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir); app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir);
@ -117,7 +119,7 @@ public final class XposedInit {
final Class<?> classActivityRes = XposedHelpers.findClassIfExists("android.app.ResourcesManager$ActivityResource", classGTLR.getClassLoader()); final Class<?> classActivityRes = XposedHelpers.findClassIfExists("android.app.ResourcesManager$ActivityResource", classGTLR.getClassLoader());
var hooker = new XC_MethodHook() { var hooker = new XC_MethodHook() {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam<?> param) {
// At least on OnePlus 5, the method has an additional parameter compared to AOSP. // At least on OnePlus 5, the method has an additional parameter compared to AOSP.
Object activityToken = null; Object activityToken = null;
try { try {
@ -162,7 +164,7 @@ public final class XposedInit {
findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class, findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class,
new XC_MethodHook() { new XC_MethodHook() {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
if (param.getResult() instanceof XResources.XTypedArray) { if (param.getResult() instanceof XResources.XTypedArray) {
return; return;
} }
@ -189,7 +191,7 @@ public final class XposedInit {
XResources.init(latestResKey); XResources.init(latestResKey);
} }
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) { private static XResources cloneToXResources(XC_MethodHook.MethodHookParam<?> param, String resDir) {
Object result = param.getResult(); Object result = param.getResult();
if (result == null || result instanceof XResources) { if (result == null || result instanceof XResources) {
return null; return null;
@ -213,21 +215,31 @@ public final class XposedInit {
return newRes; return newRes;
} }
private static final Set<String> loadedModules = new CopyOnWriteArraySet<>(); // only legacy modules have non-empty value
private static final Map<String, Optional<String>> loadedModules = new ConcurrentHashMap<>();
public static Set<String> getLoadedModules() { public static Map<String, Optional<String>> getLoadedModules() {
return loadedModules; return loadedModules;
} }
public static void loadModules() { public static void loadLegacyModules() {
var moduleList = serviceClient.getLegacyModulesList(); var moduleList = serviceClient.getLegacyModulesList();
moduleList.forEach(module -> { moduleList.forEach(module -> {
var apk = module.apkPath; var apk = module.apkPath;
var name = module.packageName; var name = module.packageName;
var file = module.file; var file = module.file;
loadedModules.add(apk); // temporarily add it for XSharedPreference loadedModules.put(name, Optional.of(apk)); // temporarily add it for XSharedPreference
if (!loadModule(name, apk, file)) { if (!loadModule(name, apk, file)) {
loadedModules.remove(apk); loadedModules.remove(name);
}
});
}
public static void loadModules(ActivityThread at) {
serviceClient.getModulesList().forEach(module -> {
loadedModules.put(module.packageName, Optional.empty());
if (!LSPosedContext.loadModule(at, module)) {
loadedModules.remove(module.packageName);
} }
}); });
} }

View File

@ -68,7 +68,7 @@ public class Startup {
// Initialize the Xposed framework // Initialize the Xposed framework
try { try {
startBootstrapHook(XposedInit.startsSystemServer); startBootstrapHook(XposedInit.startsSystemServer);
XposedInit.loadModules(); XposedInit.loadLegacyModules();
} catch (Throwable t) { } catch (Throwable t) {
Utils.logE("error during Xposed initialization", t); Utils.logE("error during Xposed initialization", t);
} }

View File

@ -6,16 +6,14 @@ import android.app.ActivityThread;
import org.lsposed.lspd.impl.LSPosedContext; import org.lsposed.lspd.impl.LSPosedContext;
import java.util.Optional;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.XposedInit;
public class AttachHooker extends XC_MethodHook { public class AttachHooker extends XC_MethodHook {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
serviceClient.getModulesList().forEach(module -> { XposedInit.loadModules((ActivityThread) param.thisObject);
if (LSPosedContext.loadModule((ActivityThread) param.thisObject, module)) {
XposedInit.getLoadedModules().add(module.packageName);
}
});
} }
} }

View File

@ -7,7 +7,7 @@ import de.robv.android.xposed.XposedBridge;
public class CrashDumpHooker extends XC_MethodHook { public class CrashDumpHooker extends XC_MethodHook {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { protected void beforeHookedMethod(MethodHookParam<?> param) {
try { try {
var e = (Throwable) param.args[0]; var e = (Throwable) param.args[0];
XposedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e)); XposedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e));

View File

@ -33,7 +33,7 @@ public class HandleSystemServerProcessHooker extends XC_MethodHook {
public static volatile ClassLoader systemServerCL; public static volatile ClassLoader systemServerCL;
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam<?> param) {
Hookers.logD("ZygoteInit#handleSystemServerProcess() starts"); Hookers.logD("ZygoteInit#handleSystemServerProcess() starts");
try { try {
// get system_server classLoader // get system_server classLoader

View File

@ -20,12 +20,15 @@
package org.lsposed.lspd.hooker; package org.lsposed.lspd.hooker;
import android.app.ActivityThread;
import android.app.LoadedApk; import android.app.LoadedApk;
import android.content.res.XResources; import android.content.res.XResources;
import android.util.Log; import android.util.Log;
import org.lsposed.lspd.util.Hookers; import org.lsposed.lspd.util.Hookers;
import java.util.Optional;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.XposedInit;
@ -34,13 +37,14 @@ import de.robv.android.xposed.XposedInit;
public class LoadedApkCtorHooker extends XC_MethodHook { public class LoadedApkCtorHooker extends XC_MethodHook {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam<?> param) {
Hookers.logD("LoadedApk#<init> starts"); Hookers.logD("LoadedApk#<init> starts");
try { try {
LoadedApk loadedApk = (LoadedApk) param.thisObject; LoadedApk loadedApk = (LoadedApk) param.thisObject;
String packageName = loadedApk.getPackageName(); String packageName = loadedApk.getPackageName();
if (XposedInit.getLoadedModules().contains(packageName)) { boolean isFirstPackage = packageName != null && ActivityThread.currentProcessName() != null && packageName.equals(ActivityThread.currentPackageName());
if (!isFirstPackage && !XposedInit.getLoadedModules().getOrDefault(packageName, Optional.of("")).isPresent()) {
return; return;
} }
Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir"); Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir");
@ -77,7 +81,7 @@ public class LoadedApkCtorHooker extends XC_MethodHook {
return; return;
} }
new LoadedApkGetCLHooker(loadedApk); new LoadedApkGetCLHooker(loadedApk, isFirstPackage);
} catch (Throwable t) { } catch (Throwable t) {
Hookers.logE("error when hooking LoadedApk.<init>", t); Hookers.logE("error when hooking LoadedApk.<init>", t);
} }

View File

@ -40,11 +40,13 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.libxposed.api.XposedModuleInterface; import io.github.libxposed.api.XposedModuleInterface;
@ -67,13 +69,16 @@ public class LoadedApkGetCLHooker extends XC_MethodHook {
private final LoadedApk loadedApk; private final LoadedApk loadedApk;
private final Unhook unhook; private final Unhook unhook;
public LoadedApkGetCLHooker(LoadedApk loadedApk) { private final boolean isFirstPackage;
public LoadedApkGetCLHooker(LoadedApk loadedApk, boolean isFirstPackage) {
this.loadedApk = loadedApk; this.loadedApk = loadedApk;
this.isFirstPackage = isFirstPackage;
unhook = XposedHelpers.findAndHookMethod(LoadedApk.class, "getClassLoader", this); unhook = XposedHelpers.findAndHookMethod(LoadedApk.class, "getClassLoader", this);
} }
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam<?> param) {
LoadedApk loadedApk = (LoadedApk) param.thisObject; LoadedApk loadedApk = (LoadedApk) param.thisObject;
if (loadedApk != this.loadedApk) { if (loadedApk != this.loadedApk) {
@ -83,15 +88,8 @@ public class LoadedApkGetCLHooker extends XC_MethodHook {
try { try {
Hookers.logD("LoadedApk#getClassLoader starts"); Hookers.logD("LoadedApk#getClassLoader starts");
String packageName = ActivityThread.currentPackageName(); final String processName = AndroidAppHelper.currentProcessName();
String processName = ActivityThread.currentProcessName(); final String packageName = isFirstPackage && "android".equals(ActivityThread.currentPackageName()) ? "system" : loadedApk.getPackageName();
boolean isFirstPackage = packageName != null && processName != null && packageName.equals(loadedApk.getPackageName());
if (!isFirstPackage) {
packageName = loadedApk.getPackageName();
processName = AndroidAppHelper.currentProcessName();
} else if (packageName.equals("android")) {
packageName = "system";
}
Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir"); Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir");
ClassLoader classLoader = (ClassLoader) param.getResult(); ClassLoader classLoader = (ClassLoader) param.getResult();
@ -109,7 +107,9 @@ public class LoadedApkGetCLHooker extends XC_MethodHook {
lpparam.appInfo = loadedApk.getApplicationInfo(); lpparam.appInfo = loadedApk.getApplicationInfo();
lpparam.isFirstApplication = isFirstPackage; lpparam.isFirstApplication = isFirstPackage;
hookNewXSP(lpparam); if (isFirstPackage && XposedInit.getLoadedModules().getOrDefault(packageName, Optional.empty()).isPresent()) {
hookNewXSP(lpparam);
}
Hookers.logD("Call handleLoadedPackage: packageName=" + lpparam.packageName + " processName=" + lpparam.processName + " isFirstPackage=" + isFirstPackage + " classLoader=" + lpparam.classLoader + " appInfo=" + lpparam.appInfo); Hookers.logD("Call handleLoadedPackage: packageName=" + lpparam.packageName + " processName=" + lpparam.processName + " isFirstPackage=" + isFirstPackage + " classLoader=" + lpparam.classLoader + " appInfo=" + lpparam.appInfo);
XC_LoadPackage.callAll(lpparam); XC_LoadPackage.callAll(lpparam);

View File

@ -9,7 +9,7 @@ import de.robv.android.xposed.XposedHelpers;
public class OpenDexFileHooker extends XC_MethodHook { public class OpenDexFileHooker extends XC_MethodHook {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
ClassLoader classLoader = null; ClassLoader classLoader = null;
for (var arg : param.args) { for (var arg : param.args) {
if (arg instanceof ClassLoader) { if (arg instanceof ClassLoader) {

View File

@ -36,7 +36,7 @@ import io.github.libxposed.api.XposedModuleInterface;
public class StartBootstrapServicesHooker extends XC_MethodHook { public class StartBootstrapServicesHooker extends XC_MethodHook {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) { protected void beforeHookedMethod(MethodHookParam<?> param) {
logD("SystemServer#startBootstrapServices() starts"); logD("SystemServer#startBootstrapServices() starts");
try { try {

View File

@ -98,7 +98,7 @@ public class ConfigManager {
private boolean verboseLog = true; private boolean verboseLog = true;
private boolean dexObfuscate = false; private boolean dexObfuscate = false;
private boolean enableStatusNotification = true; private boolean enableStatusNotification = true;
private String miscPath = null; private Path miscPath = null;
private int managerUid = -1; private int managerUid = -1;
@ -280,16 +280,15 @@ public class ConfigManager {
// Don't migrate to ConfigFileManager, as XSharedPreferences will be restored soon // Don't migrate to ConfigFileManager, as XSharedPreferences will be restored soon
String string = (String) config.get("misc_path"); String string = (String) config.get("misc_path");
if (string == null) { if (string == null) {
miscPath = "/data/misc/" + UUID.randomUUID().toString(); miscPath = Paths.get("/data", "misc", UUID.randomUUID().toString());
updateModulePrefs("lspd", 0, "config", "misc_path", miscPath); updateModulePrefs("lspd", 0, "config", "misc_path", miscPath.toString());
} else { } else {
miscPath = string; miscPath = Paths.get(string);
} }
try { try {
Path prefs = Paths.get(miscPath);
var perms = PosixFilePermissions.fromString("rwx--x--x"); var perms = PosixFilePermissions.fromString("rwx--x--x");
Files.createDirectories(prefs, PosixFilePermissions.asFileAttribute(perms)); Files.createDirectories(miscPath, PosixFilePermissions.asFileAttribute(perms));
walkFileTree(prefs, f -> SELinux.setFileContext(f.toString(), "u:object_r:magisk_file:s0")); walkFileTree(miscPath, f -> SELinux.setFileContext(f.toString(), "u:object_r:magisk_file:s0"));
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e)); Log.e(TAG, Log.getStackTraceString(e));
} }
@ -1083,9 +1082,26 @@ public class ConfigManager {
return managerUid != -1; return managerUid != -1;
} }
public String getPrefsPath(String fileName, int uid) { public String getPrefsPath(String packageName, int uid) {
int userId = uid / PER_USER_RANGE; int userId = uid / PER_USER_RANGE;
return miscPath + "/prefs" + (userId == 0 ? "" : String.valueOf(userId)) + "/" + fileName; var path = miscPath.resolve("prefs" + (userId == 0 ? "" : String.valueOf(userId))).resolve(packageName);
var module = cachedModule.getOrDefault(packageName, null);
if (module != null && module.appId == uid % PER_USER_RANGE) {
try {
var perms = PosixFilePermissions.fromString("rwx--x--x");
Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
walkFileTree(path, p -> {
try {
Os.chown(p.toString(), uid, uid);
} catch (ErrnoException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
});
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
return path.toString();
} }
// this is slow, avoid using it // this is slow, avoid using it