Fix incorrect mount point on Android 10 (#1870)
* Detect conditions when dex2oat wrapper doesn't work * No enum * Copy * Update * Update * Fix dex2oat wrapper on Android 10 * Stupid typo * Update dex2oat.cpp Co-authored-by: LoveSy <shana@zju.edu.cn>
This commit is contained in:
parent
b93bf4d25a
commit
dc6cd4b0fd
|
|
@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.ILSPManagerService;
|
||||||
import org.lsposed.lspd.models.Application;
|
import org.lsposed.lspd.models.Application;
|
||||||
import org.lsposed.lspd.models.UserInfo;
|
import org.lsposed.lspd.models.UserInfo;
|
||||||
import org.lsposed.manager.adapters.ScopeAdapter;
|
import org.lsposed.manager.adapters.ScopeAdapter;
|
||||||
|
|
@ -35,13 +36,9 @@ import org.lsposed.manager.receivers.LSPManagerServiceHolder;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import io.github.xposed.xposedservice.utils.ParceledListSlice;
|
|
||||||
|
|
||||||
public class ConfigManager {
|
public class ConfigManager {
|
||||||
|
|
||||||
public static boolean isBinderAlive() {
|
public static boolean isBinderAlive() {
|
||||||
|
|
@ -360,12 +357,12 @@ public class ConfigManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean dex2oatWrapperAlive() {
|
public static int getDex2OatWrapperCompatibility() {
|
||||||
try {
|
try {
|
||||||
return LSPManagerServiceHolder.getService().dex2oatWrapperAlive();
|
return LSPManagerServiceHolder.getService().getDex2OatWrapperCompatibility();
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||||
return false;
|
return ILSPManagerService.DEX2OAT_CRASHED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.ILSPManagerService;
|
||||||
import org.lsposed.manager.BuildConfig;
|
import org.lsposed.manager.BuildConfig;
|
||||||
import org.lsposed.manager.ConfigManager;
|
import org.lsposed.manager.ConfigManager;
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
|
|
@ -107,7 +108,7 @@ public class HomeFragment extends BaseFragment {
|
||||||
} else {
|
} else {
|
||||||
binding.updateCard.setVisibility(View.GONE);
|
binding.updateCard.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
boolean dex2oatAbnormal = !ConfigManager.dex2oatWrapperAlive() && !ConfigManager.dex2oatFlagsLoaded();
|
boolean dex2oatAbnormal = ConfigManager.getDex2OatWrapperCompatibility() != ILSPManagerService.DEX2OAT_OK && !ConfigManager.dex2oatFlagsLoaded();
|
||||||
if (!ConfigManager.isSepolicyLoaded() || !ConfigManager.systemServerRequested() || dex2oatAbnormal) {
|
if (!ConfigManager.isSepolicyLoaded() || !ConfigManager.systemServerRequested() || dex2oatAbnormal) {
|
||||||
binding.statusTitle.setText(R.string.partial_activated);
|
binding.statusTitle.setText(R.string.partial_activated);
|
||||||
binding.statusIcon.setImageResource(R.drawable.ic_round_warning_24);
|
binding.statusIcon.setImageResource(R.drawable.ic_round_warning_24);
|
||||||
|
|
@ -158,11 +159,23 @@ public class HomeFragment extends BaseFragment {
|
||||||
binding.api.setText(ConfigManager.getApi());
|
binding.api.setText(ConfigManager.getApi());
|
||||||
binding.frameworkVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%1$s (%2$d)", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
|
binding.frameworkVersion.setText(String.format(LocaleDelegate.getDefaultLocale(), "%1$s (%2$d)", ConfigManager.getXposedVersionName(), ConfigManager.getXposedVersionCode()));
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
binding.dex2oatWrapper.setText(R.string.unsupported_android_version);
|
binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.android_version_unsatisfied)));
|
||||||
} else if (ConfigManager.dex2oatWrapperAlive()) {
|
} else switch (ConfigManager.getDex2OatWrapperCompatibility()) {
|
||||||
binding.dex2oatWrapper.setText(R.string.supported);
|
case ILSPManagerService.DEX2OAT_OK:
|
||||||
} else {
|
binding.dex2oatWrapper.setText(R.string.supported);
|
||||||
binding.dex2oatWrapper.setText(R.string.unsupported_crashed);
|
break;
|
||||||
|
case ILSPManagerService.DEX2OAT_CRASHED:
|
||||||
|
binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.crashed)));
|
||||||
|
break;
|
||||||
|
case ILSPManagerService.DEX2OAT_MOUNT_FAILED:
|
||||||
|
binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.mount_failed)));
|
||||||
|
break;
|
||||||
|
case ILSPManagerService.DEX2OAT_SELINUX_PERMISSIVE:
|
||||||
|
binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.selinux_permissive)));
|
||||||
|
break;
|
||||||
|
case ILSPManagerService.DEX2OAT_SEPOLICY_INCORRECT:
|
||||||
|
binding.dex2oatWrapper.setText(String.format(LocaleDelegate.getDefaultLocale(), "%s (%s)", getString(R.string.unsupported), getString(R.string.sepolicy_incorrect)));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.apiVersion.setText(R.string.not_installed);
|
binding.apiVersion.setText(R.string.not_installed);
|
||||||
|
|
@ -187,6 +200,10 @@ public class HomeFragment extends BaseFragment {
|
||||||
"\n" +
|
"\n" +
|
||||||
binding.api.getText() +
|
binding.api.getText() +
|
||||||
"\n\n" +
|
"\n\n" +
|
||||||
|
activity.getString(R.string.info_dex2oat_wrapper) +
|
||||||
|
"\n" +
|
||||||
|
binding.dex2oatWrapper.getText() +
|
||||||
|
"\n\n" +
|
||||||
activity.getString(R.string.info_framework_version) +
|
activity.getString(R.string.info_framework_version) +
|
||||||
"\n" +
|
"\n" +
|
||||||
binding.frameworkVersion.getText() +
|
binding.frameworkVersion.getText() +
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,14 @@
|
||||||
<string name="info_device">Device</string>
|
<string name="info_device">Device</string>
|
||||||
<string name="info_system_abi">System ABI</string>
|
<string name="info_system_abi">System ABI</string>
|
||||||
<string name="info_api">Injection Interface</string>
|
<string name="info_api">Injection Interface</string>
|
||||||
<string name="info_dex2oat_wrapper">Optimizer Wrapper</string>
|
<string name="info_dex2oat_wrapper">Dex Optimizer Wrapper</string>
|
||||||
<string name="supported">Supported</string>
|
<string name="supported">Supported</string>
|
||||||
<string name="unsupported_android_version">Unsupported (Android version unsatisfied)</string>
|
<string name="unsupported">Unsupported</string>
|
||||||
<string name="unsupported_crashed">Unsupported (Crashed)</string>
|
<string name="android_version_unsatisfied">Android version unsatisfied</string>
|
||||||
|
<string name="crashed">Crashed</string>
|
||||||
|
<string name="mount_failed">Mount failed</string>
|
||||||
|
<string name="selinux_permissive">SELinux is permissive</string>
|
||||||
|
<string name="sepolicy_incorrect">SELinux policy is incorrect</string>
|
||||||
<string name="parasitic_recommend">Parasitic Manager Recommended</string>
|
<string name="parasitic_recommend">Parasitic Manager Recommended</string>
|
||||||
<string name="parasitic_recommend_summary">LSPosed now supports system parasitization to avoid detection. You can safely uninstall the manager after successfully creating a shortcut of the parasitic manager. In any case you can install the manager back from /data/adb/lspd/manager.apk.</string>
|
<string name="parasitic_recommend_summary">LSPosed now supports system parasitization to avoid detection. You can safely uninstall the manager after successfully creating a shortcut of the parasitic manager. In any case you can install the manager back from /data/adb/lspd/manager.apk.</string>
|
||||||
<string name="create_shortcut">Create shortcut</string>
|
<string name="create_shortcut">Create shortcut</string>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@
|
||||||
-keepclasseswithmembers class org.lsposed.lspd.Main {
|
-keepclasseswithmembers class org.lsposed.lspd.Main {
|
||||||
public static void main(java.lang.String[]);
|
public static void main(java.lang.String[]);
|
||||||
}
|
}
|
||||||
|
-keepclasseswithmembers class org.lsposed.lspd.service.Dex2OatService {
|
||||||
|
private java.lang.String devTmpDir;
|
||||||
|
private java.lang.String magiskPath;
|
||||||
|
private java.lang.String fakeBin32;
|
||||||
|
private java.lang.String fakeBin64;
|
||||||
|
private java.lang.String[] dex2oatBinaries;
|
||||||
|
}
|
||||||
-keepclasseswithmembers class org.lsposed.lspd.service.LogcatService {
|
-keepclasseswithmembers class org.lsposed.lspd.service.LogcatService {
|
||||||
private int refreshFd(boolean);
|
private int refreshFd(boolean);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,17 @@
|
||||||
|
|
||||||
package org.lsposed.lspd.service;
|
package org.lsposed.lspd.service;
|
||||||
|
|
||||||
|
import static org.lsposed.lspd.ILSPManagerService.DEX2OAT_CRASHED;
|
||||||
|
import static org.lsposed.lspd.ILSPManagerService.DEX2OAT_MOUNT_FAILED;
|
||||||
|
import static org.lsposed.lspd.ILSPManagerService.DEX2OAT_OK;
|
||||||
|
import static org.lsposed.lspd.ILSPManagerService.DEX2OAT_SELINUX_PERMISSIVE;
|
||||||
|
import static org.lsposed.lspd.ILSPManagerService.DEX2OAT_SEPOLICY_INCORRECT;
|
||||||
|
|
||||||
import android.net.LocalServerSocket;
|
import android.net.LocalServerSocket;
|
||||||
import android.net.LocalSocket;
|
import android.net.LocalSocket;
|
||||||
import android.net.LocalSocketAddress;
|
import android.net.LocalSocketAddress;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.FileObserver;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.SELinux;
|
import android.os.SELinux;
|
||||||
import android.system.ErrnoException;
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
|
|
@ -32,88 +37,184 @@ import android.system.OsConstants;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class Dex2OatService {
|
public class Dex2OatService {
|
||||||
|
|
||||||
public static final String PROP_NAME = "dalvik.vm.dex2oat-flags";
|
public static final String PROP_NAME = "dalvik.vm.dex2oat-flags";
|
||||||
public static final String PROP_VALUE = "--inline-max-code-units=0";
|
public static final String PROP_VALUE = "--inline-max-code-units=0";
|
||||||
private static final String TAG = "LSPosedDex2Oat";
|
private static final String TAG = "LSPosedDex2Oat";
|
||||||
private static final String DEX2OAT_32 = "/apex/com.android.art/bin/dex2oat32";
|
|
||||||
private static final String DEX2OAT_64 = "/apex/com.android.art/bin/dex2oat64";
|
|
||||||
|
|
||||||
private final Thread thread;
|
private String devTmpDir, magiskPath, fakeBin32, fakeBin64;
|
||||||
|
private String[] dex2oatBinaries;
|
||||||
|
private FileDescriptor[] stockFds;
|
||||||
private LocalSocket serverSocket = null;
|
private LocalSocket serverSocket = null;
|
||||||
private LocalServerSocket server = null;
|
private LocalServerSocket server = null;
|
||||||
private FileDescriptor stockFd32 = null, stockFd64 = null;
|
private int compatibility = DEX2OAT_OK;
|
||||||
|
|
||||||
|
private final FileObserver selinuxObserver = new FileObserver("/sys/fs/selinux/enforce", FileObserver.MODIFY) {
|
||||||
|
@Override
|
||||||
|
public void onEvent(int i, @Nullable String s) {
|
||||||
|
Log.d(TAG, "SELinux status changed");
|
||||||
|
synchronized (this) {
|
||||||
|
if (compatibility == DEX2OAT_CRASHED) stopWatching();
|
||||||
|
boolean enforcing = false;
|
||||||
|
try (var is = Files.newInputStream(Paths.get("/sys/fs/selinux/enforce"))) {
|
||||||
|
enforcing = is.read() == '1';
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
if (!enforcing) {
|
||||||
|
if (compatibility == DEX2OAT_OK) setEnabled(false);
|
||||||
|
compatibility = DEX2OAT_SELINUX_PERMISSIVE;
|
||||||
|
} else if (SELinux.checkSELinuxAccess("u:r:untrusted_app:s0", "u:object_r:dex2oat_exec:s0", "file", "execute")
|
||||||
|
|| SELinux.checkSELinuxAccess("u:r:untrusted_app:s0", "u:object_r:dex2oat_exec:s0", "file", "execute_no_trans")) {
|
||||||
|
if (compatibility == DEX2OAT_OK) setEnabled(false);
|
||||||
|
compatibility = DEX2OAT_SEPOLICY_INCORRECT;
|
||||||
|
} else {
|
||||||
|
if (compatibility != DEX2OAT_OK) {
|
||||||
|
setEnabled(true);
|
||||||
|
if (checkMount()) compatibility = DEX2OAT_OK;
|
||||||
|
else {
|
||||||
|
setEnabled(false);
|
||||||
|
compatibility = DEX2OAT_MOUNT_FAILED;
|
||||||
|
stopWatching();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopWatching() {
|
||||||
|
super.stopWatching();
|
||||||
|
Log.w(TAG, "SELinux observer stopped");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
public Dex2OatService() {
|
public Dex2OatService() {
|
||||||
thread = new Thread(() -> {
|
initNative();
|
||||||
var devPath = Paths.get(getDevPath());
|
try {
|
||||||
|
Files.walk(Paths.get(magiskPath).resolve("dex2oat")).forEach(path -> {
|
||||||
|
SELinux.setFileContext(path.toString(), "u:object_r:magisk_file:s0");
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error setting sepolicy", e);
|
||||||
|
}
|
||||||
|
if (Arrays.stream(dex2oatBinaries).noneMatch(Objects::nonNull)) {
|
||||||
|
Log.e(TAG, "Failed to find dex2oat binaries");
|
||||||
|
compatibility = DEX2OAT_MOUNT_FAILED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkMount()) { // Already mounted when restart daemon
|
||||||
|
setEnabled(true);
|
||||||
|
if (!checkMount()) {
|
||||||
|
setEnabled(false);
|
||||||
|
compatibility = DEX2OAT_MOUNT_FAILED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread daemonThread = new Thread(() -> {
|
||||||
|
var devPath = Paths.get(devTmpDir);
|
||||||
var sockPath = devPath.resolve("dex2oat.sock");
|
var sockPath = devPath.resolve("dex2oat.sock");
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "dex2oat daemon start");
|
Log.i(TAG, "Daemon start");
|
||||||
if (setSocketCreateContext("u:r:dex2oat:s0")) {
|
if (setSocketCreateContext("u:r:dex2oat:s0")) {
|
||||||
Log.d(TAG, "set socket context to u:r:dex2oat:s0");
|
Log.d(TAG, "Set socket context to u:r:dex2oat:s0");
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("failed to set socket context");
|
throw new IOException("Failed to set socket context");
|
||||||
}
|
}
|
||||||
Files.createDirectories(devPath);
|
Files.createDirectories(devPath);
|
||||||
Log.d(TAG, "dev path: " + devPath);
|
Log.d(TAG, "Dev path: " + devPath);
|
||||||
|
|
||||||
serverSocket = new LocalSocket(LocalSocket.SOCKET_STREAM);
|
serverSocket = new LocalSocket(LocalSocket.SOCKET_STREAM);
|
||||||
serverSocket.bind(new LocalSocketAddress(sockPath.toString(), LocalSocketAddress.Namespace.FILESYSTEM));
|
serverSocket.bind(new LocalSocketAddress(sockPath.toString(), LocalSocketAddress.Namespace.FILESYSTEM));
|
||||||
server = new LocalServerSocket(serverSocket.getFileDescriptor());
|
server = new LocalServerSocket(serverSocket.getFileDescriptor());
|
||||||
SELinux.setFileContext(sockPath.toString(), "u:object_r:magisk_file:s0");
|
SELinux.setFileContext(sockPath.toString(), "u:object_r:magisk_file:s0");
|
||||||
if (new File(DEX2OAT_32).exists()) stockFd32 = Os.open(DEX2OAT_32, OsConstants.O_RDONLY, 0);
|
stockFds = new FileDescriptor[dex2oatBinaries.length];
|
||||||
if (new File(DEX2OAT_64).exists()) stockFd64 = Os.open(DEX2OAT_64, OsConstants.O_RDONLY, 0);
|
for (int i = 0; i < dex2oatBinaries.length; i++) {
|
||||||
|
if (dex2oatBinaries[i] != null) {
|
||||||
|
stockFds[i] = Os.open(dex2oatBinaries[i], OsConstants.O_RDONLY, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var client = server.accept();
|
var client = server.accept();
|
||||||
try (var is = client.getInputStream();
|
try (var is = client.getInputStream();
|
||||||
var os = client.getOutputStream()) {
|
var os = client.getOutputStream()) {
|
||||||
var lp = is.read();
|
var id = is.read();
|
||||||
if (lp == 32) client.setFileDescriptorsForSend(new FileDescriptor[]{stockFd32});
|
client.setFileDescriptorsForSend(new FileDescriptor[]{stockFds[id]});
|
||||||
else client.setFileDescriptorsForSend(new FileDescriptor[]{stockFd64});
|
|
||||||
os.write(1);
|
os.write(1);
|
||||||
Log.d(TAG, "sent fd" + lp);
|
Log.d(TAG, String.format("Sent stock fd: is64 = %b, isDebug = %b", (id & 0b10) != 0, (id & 0b01) != 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "dex2oat daemon crashed", e);
|
Log.e(TAG, "Daemon crashed", e);
|
||||||
try {
|
try {
|
||||||
server.close();
|
server.close();
|
||||||
Files.delete(sockPath);
|
Files.delete(sockPath);
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (stockFd32 != null && stockFd32.valid()) Os.close(stockFd32);
|
for (var fd : stockFds) {
|
||||||
if (stockFd64 != null && stockFd64.valid()) Os.close(stockFd64);
|
if (fd != null && fd.valid()) Os.close(fd);
|
||||||
|
}
|
||||||
} catch (ErrnoException ignored) {
|
} catch (ErrnoException ignored) {
|
||||||
}
|
}
|
||||||
new Handler(Looper.getMainLooper()).post(Dex2OatService::fallback);
|
synchronized (this) {
|
||||||
|
selinuxObserver.stopWatching();
|
||||||
|
if (compatibility == DEX2OAT_OK) {
|
||||||
|
setEnabled(false);
|
||||||
|
compatibility = DEX2OAT_CRASHED;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
thread.start();
|
|
||||||
|
daemonThread.start();
|
||||||
|
selinuxObserver.startWatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
public boolean isAlive() {
|
public int getCompatibility() {
|
||||||
return thread.isAlive();
|
return compatibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static native String getDevPath();
|
private native void initNative();
|
||||||
|
|
||||||
private static native void fallback();
|
private native void setEnabled(boolean enabled);
|
||||||
|
|
||||||
|
private boolean checkMount() {
|
||||||
|
for (int i = 0; i < dex2oatBinaries.length; i++) {
|
||||||
|
var bin = dex2oatBinaries[i];
|
||||||
|
if (bin == null) continue;
|
||||||
|
try {
|
||||||
|
var apex = Os.stat("/proc/1/root" + bin);
|
||||||
|
var fake = Os.stat(i < 2 ? fakeBin32 : fakeBin64);
|
||||||
|
if (apex.st_ino != fake.st_ino) {
|
||||||
|
Log.w(TAG, "Check mount failed for " + bin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
Log.e(TAG, "Check mount failed for " + bin, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Check mount succeeded");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean setSocketCreateContext(String context) {
|
private boolean setSocketCreateContext(String context) {
|
||||||
FileDescriptor fd = null;
|
FileDescriptor fd = null;
|
||||||
|
|
|
||||||
|
|
@ -777,11 +777,11 @@ public class LSPManagerService extends ILSPManagerService.Stub {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dex2oatWrapperAlive() {
|
public int getDex2OatWrapperCompatibility() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
return ServiceManager.getDex2OatService().isAlive();
|
return ServiceManager.getDex2OatService().getCompatibility();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.4.1)
|
||||||
add_subdirectory(${EXTERNAL_ROOT} external)
|
add_subdirectory(${EXTERNAL_ROOT} external)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
dex2oat.c
|
dex2oat.cpp
|
||||||
logcat.cpp
|
logcat.cpp
|
||||||
obfuscation.cpp
|
obfuscation.cpp
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* This file is part of LSPosed.
|
||||||
|
*
|
||||||
|
* LSPosed is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LSPosed is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Created by Nullptr on 2022/4/2.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
const char *kDex2oat32Path, *kDex2oatDebug32Path;
|
||||||
|
const char *kDex2oat64Path, *kDex2oatDebug64Path;
|
||||||
|
|
||||||
|
char kFakeBin32[PATH_MAX], kFakeBin64[PATH_MAX];
|
||||||
|
char kTmpDir[] = "placeholder_/dev/0123456789abcdef\0";
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_lsposed_lspd_service_Dex2OatService_initNative(JNIEnv *env, jobject thiz) {
|
||||||
|
char magisk_path[PATH_MAX], cwd[PATH_MAX], *module_name;
|
||||||
|
FILE *fp = popen("magisk --path", "r");
|
||||||
|
fscanf(fp, "%s", magisk_path);
|
||||||
|
fclose(fp);
|
||||||
|
getcwd(cwd, PATH_MAX);
|
||||||
|
module_name = cwd + std::string_view(cwd).find_last_of('/') + 1;
|
||||||
|
sprintf(kFakeBin32, "%s/.magisk/modules/%s/bin/dex2oat32", magisk_path, module_name);
|
||||||
|
sprintf(kFakeBin64, "%s/.magisk/modules/%s/bin/dex2oat64", magisk_path, module_name);
|
||||||
|
|
||||||
|
if (GetAndroidApiLevel() == 29) {
|
||||||
|
kDex2oat32Path = "/apex/com.android.runtime/bin/dex2oat";
|
||||||
|
kDex2oatDebug32Path = "/apex/com.android.runtime/bin/dex2oatd";
|
||||||
|
} else {
|
||||||
|
kDex2oat32Path = "/apex/com.android.art/bin/dex2oat32";
|
||||||
|
kDex2oatDebug32Path = "/apex/com.android.art/bin/dex2oatd32";
|
||||||
|
kDex2oat64Path = "/apex/com.android.art/bin/dex2oat64";
|
||||||
|
kDex2oatDebug64Path = "/apex/com.android.art/bin/dex2oatd64";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string copy_dir = magisk_path + "/dex2oat"s;
|
||||||
|
mkdir(copy_dir.c_str(), 0755);
|
||||||
|
auto CopyAndMount = [&](const char *src, const std::string &target) -> bool {
|
||||||
|
int stock = open(src, O_RDONLY);
|
||||||
|
if (stock == -1) return false;
|
||||||
|
struct stat st{};
|
||||||
|
fstat(stock, &st);
|
||||||
|
int copy = open(target.c_str(), O_WRONLY | O_CREAT, st.st_mode);
|
||||||
|
sendfile(copy, stock, nullptr, st.st_size);
|
||||||
|
close(stock);
|
||||||
|
close(copy);
|
||||||
|
mount(target.c_str(), src, nullptr, MS_BIND, nullptr);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
bool done[4] = {false};
|
||||||
|
done[0] = CopyAndMount(kDex2oat32Path, copy_dir + "/dex2oat32");
|
||||||
|
done[1] = CopyAndMount(kDex2oatDebug32Path, copy_dir + "/dex2oatd32");
|
||||||
|
if (GetAndroidApiLevel() >= 30) {
|
||||||
|
done[2] = CopyAndMount(kDex2oat64Path, copy_dir + "/dex2oat64");
|
||||||
|
done[3] = CopyAndMount(kDex2oatDebug64Path, copy_dir + "/dex2oatd64");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clazz = env->GetObjectClass(thiz);
|
||||||
|
auto dev_path_field = env->GetFieldID(clazz, "devTmpDir", "Ljava/lang/String;");
|
||||||
|
auto magisk_path_field = env->GetFieldID(clazz, "magiskPath", "Ljava/lang/String;");
|
||||||
|
auto fake_bin32_field = env->GetFieldID(clazz, "fakeBin32", "Ljava/lang/String;");
|
||||||
|
auto fake_bin64_field = env->GetFieldID(clazz, "fakeBin64", "Ljava/lang/String;");
|
||||||
|
auto binaries_field = env->GetFieldID(clazz, "dex2oatBinaries", "[Ljava/lang/String;");
|
||||||
|
env->SetObjectField(thiz, dev_path_field, env->NewStringUTF(kTmpDir + 12));
|
||||||
|
env->SetObjectField(thiz, magisk_path_field, env->NewStringUTF(magisk_path));
|
||||||
|
env->SetObjectField(thiz, fake_bin32_field, env->NewStringUTF(kFakeBin32));
|
||||||
|
env->SetObjectField(thiz, fake_bin64_field, env->NewStringUTF(kFakeBin64));
|
||||||
|
auto arr = env->NewObjectArray(4, env->FindClass("java/lang/String"), nullptr);
|
||||||
|
if (done[0]) env->SetObjectArrayElement(arr, 0, env->NewStringUTF(kDex2oat32Path));
|
||||||
|
if (done[1]) env->SetObjectArrayElement(arr, 1, env->NewStringUTF(kDex2oatDebug32Path));
|
||||||
|
if (GetAndroidApiLevel() >= 30) {
|
||||||
|
if (done[2]) env->SetObjectArrayElement(arr, 2, env->NewStringUTF(kDex2oat64Path));
|
||||||
|
if (done[3]) env->SetObjectArrayElement(arr, 3, env->NewStringUTF(kDex2oatDebug64Path));
|
||||||
|
}
|
||||||
|
env->SetObjectField(thiz, binaries_field, arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_lsposed_lspd_service_Dex2OatService_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
|
||||||
|
if (vfork() == 0) {
|
||||||
|
int ns = open("/proc/1/ns/mnt", O_RDONLY);
|
||||||
|
setns(ns, CLONE_NEWNS);
|
||||||
|
close(ns);
|
||||||
|
if (enabled) {
|
||||||
|
LOGI("Enable dex2oat wrapper");
|
||||||
|
mount(kFakeBin32, kDex2oat32Path, nullptr, MS_BIND, nullptr);
|
||||||
|
mount(kFakeBin32, kDex2oatDebug32Path, nullptr, MS_BIND, nullptr);
|
||||||
|
if (GetAndroidApiLevel() >= 30) {
|
||||||
|
mount(kFakeBin64, kDex2oat64Path, nullptr, MS_BIND, nullptr);
|
||||||
|
mount(kFakeBin64, kDex2oatDebug64Path, nullptr, MS_BIND, nullptr);
|
||||||
|
}
|
||||||
|
execlp("resetprop", "resetprop", "--delete", "dalvik.vm.dex2oat-flags", nullptr);
|
||||||
|
} else {
|
||||||
|
LOGI("Disable dex2oat wrapper");
|
||||||
|
umount(kDex2oat32Path);
|
||||||
|
umount(kDex2oatDebug32Path);
|
||||||
|
if (GetAndroidApiLevel() >= 30) {
|
||||||
|
umount(kDex2oat64Path);
|
||||||
|
umount(kDex2oatDebug64Path);
|
||||||
|
}
|
||||||
|
execlp("resetprop", "resetprop", "dalvik.vm.dex2oat-flags", "--inline-max-code-units=0", nullptr);
|
||||||
|
}
|
||||||
|
PLOGE("Failed to resetprop");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,26 +18,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
// Created by Nullptr on 2022/4/2.
|
// Created by Nullptr on 2022/4/20.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <jni.h>
|
#pragma once
|
||||||
#include <sys/system_properties.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "logging.h"
|
inline int32_t GetAndroidApiLevel() {
|
||||||
|
static int32_t api_level = []() {
|
||||||
char kTmpDir[] = "placeholder_/dev/0123456789abcdef";
|
char prop_value[PROP_VALUE_MAX];
|
||||||
|
__system_property_get("ro.build.version.sdk", prop_value);
|
||||||
JNIEXPORT jstring JNICALL
|
int base = atoi(prop_value);
|
||||||
Java_org_lsposed_lspd_service_Dex2OatService_getDevPath(JNIEnv *env, jclass clazz) {
|
__system_property_get("ro.build.version.preview_sdk", prop_value);
|
||||||
return (*env)->NewStringUTF(env, kTmpDir + 12);
|
return base + atoi(prop_value);
|
||||||
}
|
}();
|
||||||
|
return api_level;
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_org_lsposed_lspd_service_Dex2OatService_fallback(JNIEnv *env, jclass clazz) {
|
|
||||||
LOGI("do fallback");
|
|
||||||
system("nsenter -m -t 1 umount /apex/com.android.art/bin/dex2oat*");
|
|
||||||
__system_property_set("dalvik.vm.dex2oat-flags", "--inline-max-code-units=0");
|
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +36,8 @@
|
||||||
# define LP_SELECT(lp32, lp64) lp32
|
# define LP_SELECT(lp32, lp64) lp32
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define ID_VEC(is64, is_debug) (((is64) << 1) | (is_debug))
|
||||||
|
|
||||||
char kTmpDir[] = "placeholder_/dev/0123456789abcdef";
|
char kTmpDir[] = "placeholder_/dev/0123456789abcdef";
|
||||||
|
|
||||||
static ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
|
static ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) {
|
||||||
|
|
@ -106,7 +108,7 @@ int main(int argc, char **argv) {
|
||||||
PLOGE("failed to connect to %s", sock.sun_path);
|
PLOGE("failed to connect to %s", sock.sun_path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
write_int(sock_fd, LP_SELECT(32, 64));
|
write_int(sock_fd, ID_VEC(LP_SELECT(0, 1), strstr(argv[0], "dex2oatd") != NULL));
|
||||||
int stock_fd = recv_fd(sock_fd);
|
int stock_fd = recv_fd(sock_fd);
|
||||||
read_int(sock_fd);
|
read_int(sock_fd);
|
||||||
close(sock_fd);
|
close(sock_fd);
|
||||||
|
|
|
||||||
|
|
@ -40,15 +40,5 @@ if [ ! -S "/dev/socket/zygote" ]; then
|
||||||
fi
|
fi
|
||||||
$debug && log -p d -t "LSPosed" "start $flavor daemon $*"
|
$debug && log -p d -t "LSPosed" "start $flavor daemon $*"
|
||||||
|
|
||||||
if [ "$(getprop ro.build.version.sdk)" -ge 29 ]; then
|
|
||||||
umount /apex/com.android.art/bin/dex2oat*
|
|
||||||
mkdir "$magiskPath/dex2oat"
|
|
||||||
cp -p /apex/com.android.art/bin/dex2oat32 "$magiskPath/dex2oat/dex2oat32"
|
|
||||||
cp -p /apex/com.android.art/bin/dex2oat64 "$magiskPath/dex2oat/dex2oat64"
|
|
||||||
chcon -R u:object_r:magisk_file:s0 "$magiskPath/dex2oat"
|
|
||||||
mount --bind "$magiskPath/dex2oat/dex2oat32" /apex/com.android.art/bin/dex2oat32
|
|
||||||
mount --bind "$magiskPath/dex2oat/dex2oat64" /apex/com.android.art/bin/dex2oat64
|
|
||||||
fi
|
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
exec /system/bin/app_process $java_options /system/bin --nice-name=lspd org.lsposed.lspd.Main "$@" >/dev/null 2>&1
|
exec /system/bin/app_process $java_options /system/bin --nice-name=lspd org.lsposed.lspd.Main "$@" >/dev/null 2>&1
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
MODDIR=${0%/*}
|
MODDIR=${0%/*}
|
||||||
MODNAME=${MODDIR##*/}
|
|
||||||
MAGISK_PATH=$(magisk --path)
|
|
||||||
|
|
||||||
rm -f "/data/local/tmp/daemon.apk"
|
rm -f "/data/local/tmp/daemon.apk"
|
||||||
cd "$MODDIR"
|
cd "$MODDIR"
|
||||||
|
|
||||||
if [ "$(getprop ro.build.version.sdk)" -ge 29 ]; then
|
|
||||||
mount --bind "$MAGISK_PATH/.magisk/modules/$MODNAME/bin/dex2oat32" "/apex/com.android.art/bin/dex2oat32"
|
|
||||||
mount --bind "$MAGISK_PATH/.magisk/modules/$MODNAME/bin/dex2oat64" "/apex/com.android.art/bin/dex2oat64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
unshare -m sh -c "$MODDIR/daemon &"
|
unshare -m sh -c "$MODDIR/daemon &"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ import org.lsposed.lspd.models.Application;
|
||||||
|
|
||||||
|
|
||||||
interface ILSPManagerService {
|
interface ILSPManagerService {
|
||||||
|
const int DEX2OAT_OK = 0;
|
||||||
|
const int DEX2OAT_CRASHED = 1;
|
||||||
|
const int DEX2OAT_MOUNT_FAILED = 2;
|
||||||
|
const int DEX2OAT_SELINUX_PERMISSIVE = 3;
|
||||||
|
const int DEX2OAT_SEPOLICY_INCORRECT = 4;
|
||||||
|
|
||||||
String getApi() = 1;
|
String getApi() = 1;
|
||||||
|
|
||||||
ParceledListSlice<PackageInfo> getInstalledPackagesFromAllUsers(int flags, boolean filterNoProcess) = 2;
|
ParceledListSlice<PackageInfo> getInstalledPackagesFromAllUsers(int flags, boolean filterNoProcess) = 2;
|
||||||
|
|
@ -80,5 +86,5 @@ interface ILSPManagerService {
|
||||||
|
|
||||||
void setDexObfuscate(boolean enable) = 43;
|
void setDexObfuscate(boolean enable) = 43;
|
||||||
|
|
||||||
boolean dex2oatWrapperAlive() = 44;
|
int getDex2OatWrapperCompatibility() = 44;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue