Skip to content
This repository was archived by the owner on Jun 1, 2023. It is now read-only.

Commit 0b4ec2c

Browse files
committed
Close certain files temporarily before forking
This is to work around a new security feature introduced by Google in the November 16 patches. It would abort the runtime if files are open that are not whitelisted. To avoid this, we close the files ourselves and reopen them after Zygote has forked. The mechanism is the same that Google has introduced, with a few modifications to fit Xposed's needs. This requires also a change on the native side. Fixes #129.
1 parent 9e88044 commit 0b4ec2c

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

app/src/main/java/de/robv/android/xposed/XposedBridge.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ public static Object invokeOriginalMethod(Member method, Object thisObject, Obje
486486

487487
private static native Object cloneToSubclassNative(Object obj, Class<?> targetClazz);
488488

489+
/*package*/ static native void closeFilesBeforeForkNative();
490+
/*package*/ static native void reopenFilesAfterForkNative();
491+
489492
/** @hide */
490493
public static final class CopyOnWriteSortedSet<E> {
491494
private transient volatile Object[] elements = EMPTY_ARRAY;

app/src/main/java/de/robv/android/xposed/XposedHelpers.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
import android.content.res.AssetManager;
44
import android.content.res.Resources;
55

6+
import java.io.BufferedReader;
67
import java.io.ByteArrayOutputStream;
78
import java.io.Closeable;
9+
import java.io.File;
810
import java.io.FileInputStream;
11+
import java.io.FileReader;
912
import java.io.IOException;
1013
import java.io.InputStream;
1114
import java.lang.reflect.Constructor;
@@ -1598,6 +1601,24 @@ protected AtomicInteger initialValue() {
15981601
}
15991602
}
16001603

1604+
/*package*/ static boolean fileContains(File file, String str) throws IOException {
1605+
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
1606+
// but the naive approach should be sufficient here.
1607+
BufferedReader in = null;
1608+
try {
1609+
in = new BufferedReader(new FileReader(file));
1610+
String line;
1611+
while ((line = in.readLine()) != null) {
1612+
if (line.contains(str)) {
1613+
return true;
1614+
}
1615+
}
1616+
return false;
1617+
} finally {
1618+
closeSilently(in);
1619+
}
1620+
}
1621+
16011622
//#################################################################################################
16021623
// TODO helpers for view traversing
16031624
/*To make it easier, I will try and implement some more helpers:

app/src/main/java/de/robv/android/xposed/XposedInit.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.content.res.TypedArray;
1212
import android.content.res.XResources;
1313
import android.os.Build;
14+
import android.os.Environment;
1415
import android.os.Process;
1516
import android.util.Log;
1617

@@ -36,7 +37,9 @@
3637
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
3738
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
3839
import static de.robv.android.xposed.XposedHelpers.closeSilently;
40+
import static de.robv.android.xposed.XposedHelpers.fileContains;
3941
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
42+
import static de.robv.android.xposed.XposedHelpers.findClass;
4043
import static de.robv.android.xposed.XposedHelpers.getBooleanField;
4144
import static de.robv.android.xposed.XposedHelpers.getObjectField;
4245
import static de.robv.android.xposed.XposedHelpers.setObjectField;
@@ -61,6 +64,24 @@ private XposedInit() {}
6164
* Hook some methods which we want to create an easier interface for developers.
6265
*/
6366
/*package*/ static void initForZygote() throws Throwable {
67+
if (needsToCloseFilesForFork()) {
68+
XC_MethodHook callback = new XC_MethodHook() {
69+
@Override
70+
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
71+
XposedBridge.closeFilesBeforeForkNative();
72+
}
73+
74+
@Override
75+
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
76+
XposedBridge.reopenFilesAfterForkNative();
77+
}
78+
};
79+
80+
Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
81+
hookAllMethods(zygote, "nativeForkAndSpecialize", callback);
82+
hookAllMethods(zygote, "nativeForkSystemServer", callback);
83+
}
84+
6485
final HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
6586

6687
// normal process initialization (for new Activity, Service, BroadcastReceiver etc.)
@@ -332,6 +353,23 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
332353
XResources.init(latestResKey);
333354
}
334355

356+
private static boolean needsToCloseFilesForFork() {
357+
if (Build.VERSION.SDK_INT >= 24) {
358+
return true;
359+
} else if (Build.VERSION.SDK_INT < 21) {
360+
return false;
361+
}
362+
363+
File lib = new File(Environment.getRootDirectory(), "lib/libandroid_runtime.so");
364+
try {
365+
return fileContains(lib, "Unable to construct file descriptor table");
366+
} catch (IOException e) {
367+
Log.e(TAG, "Could not check whether " + lib + " has security patch level 5");
368+
// In doubt, just do it. The worst case should be unnecessary work and log messages.
369+
return true;
370+
}
371+
}
372+
335373
private static void hookXposedInstaller(ClassLoader classLoader) {
336374
try {
337375
findAndHookMethod(INSTALLER_PACKAGE_NAME + ".XposedApp", classLoader, "getActiveXposedVersion",

0 commit comments

Comments
 (0)