diff --git a/daemon/src/main/java/org/lsposed/lspd/service/Dex2OatService.java b/daemon/src/main/java/org/lsposed/lspd/service/Dex2OatService.java new file mode 100644 index 00000000..f4f01c05 --- /dev/null +++ b/daemon/src/main/java/org/lsposed/lspd/service/Dex2OatService.java @@ -0,0 +1,152 @@ +/* + * 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 . + * + * Copyright (C) 2022 LSPosed Contributors + */ + +package org.lsposed.lspd.service; + +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.net.LocalSocketAddress; +import android.os.SELinux; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Locale; + +public class Dex2OatService { + + private static final String TAG = "Dex2OatService"; + 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 Thread thread = null; + + public void start() { + try { + var devPath = Files.readAllLines(Paths.get("/data/adb/lspd/dev_path")).get(0); + Log.d(TAG, "dev path: " + devPath); + daemon(devPath); + } catch (IOException e) { + Log.e(TAG, "dex2oat daemon failed to start", e); + } + } + + public boolean isAlive() { + return thread.isAlive(); + } + + private void daemon(String devPath) { + thread = new Thread(() -> { + try { + Log.i(TAG, "dex2oat daemon start"); + if (setSocketCreateContext("u:r:dex2oat:s0")) { + Log.d(TAG, "set socket context to u:r:dex2oat:s0"); + } else { + Log.e(TAG, "failed to set socket context"); + } + var sockPath = devPath + "/dex2oat.sock"; + var serverSocket = new LocalSocket(LocalSocket.SOCKET_STREAM); + serverSocket.bind(new LocalSocketAddress(sockPath, LocalSocketAddress.Namespace.FILESYSTEM)); + var server = new LocalServerSocket(serverSocket.getFileDescriptor()); + SELinux.setFileContext(sockPath, "u:object_r:magisk_file:s0"); + FileDescriptor stockFd32 = null, stockFd64 = null; + if (new File(DEX2OAT_32).exists()) stockFd32 = Os.open(DEX2OAT_32, OsConstants.O_RDONLY, 0); + if (new File(DEX2OAT_64).exists()) stockFd64 = Os.open(DEX2OAT_64, OsConstants.O_RDONLY, 0); + while (true) { + var client = server.accept(); + try (var is = client.getInputStream(); + var os = client.getOutputStream()) { + var lp = is.read(); + if (lp == 32) client.setFileDescriptorsForSend(new FileDescriptor[]{stockFd32}); + else client.setFileDescriptorsForSend(new FileDescriptor[]{stockFd64}); + os.write(1); + Log.d(TAG, "sent fd" + lp); + } + } + } catch (Exception e) { + Log.e(TAG, "dex2oat daemon crashed", e); + } + }); + thread.start(); + } + + private boolean setSocketCreateContext(String context) { + FileDescriptor fd = null; + try { + fd = Os.open("/proc/thread-self/attr/sockcreate", OsConstants.O_RDWR, 0); + } catch (ErrnoException e) { + if (e.errno == OsConstants.ENOENT) { + int tid = Os.gettid(); + try { + fd = Os.open(String.format(Locale.ENGLISH, "/proc/self/task/%d/attr/sockcreate", tid), OsConstants.O_RDWR, 0); + } catch (ErrnoException ignored) { + } + } + } + + if (fd == null) { + return false; + } + + byte[] bytes; + int length; + int remaining; + if (!TextUtils.isEmpty(context)) { + byte[] stringBytes = context.getBytes(); + bytes = new byte[stringBytes.length + 1]; + System.arraycopy(stringBytes, 0, bytes, 0, stringBytes.length); + bytes[stringBytes.length] = '\0'; + + length = bytes.length; + remaining = bytes.length; + } else { + bytes = null; + length = 0; + remaining = 0; + } + + do { + try { + remaining -= Os.write(fd, bytes, length - remaining, remaining); + if (remaining <= 0) { + break; + } + } catch (ErrnoException e) { + break; + } catch (InterruptedIOException e) { + remaining -= e.bytesTransferred; + } + } while (true); + + try { + Os.close(fd); + } catch (ErrnoException e) { + Log.w(TAG, Log.getStackTraceString(e)); + } + return true; + } +} diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java index 2f603710..fa5a0fe6 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPManagerService.java @@ -688,7 +688,11 @@ public class LSPManagerService extends ILSPManagerService.Stub { @Override public boolean dex2oatFlagsLoaded() { - return SystemProperties.get(PROP_NAME).contains(PROP_VALUE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return ServiceManager.getDex2OatService().isAlive(); + } else { + return SystemProperties.get(PROP_NAME).contains(PROP_VALUE); + } } @Override diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java index 90a0adaf..182bb24d 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java @@ -22,6 +22,7 @@ package org.lsposed.lspd.service; import android.app.ActivityThread; import android.content.Context; import android.ddm.DdmHandleAppName; +import android.os.Build; import android.os.IBinder; import android.os.IServiceManager; import android.os.Looper; @@ -49,9 +50,14 @@ public class ServiceManager { private static LSPManagerService managerService = null; private static LSPSystemServerService systemServerService = null; private static LogcatService logcatService = null; + private static Dex2OatService dex2OatService = null; private static final ExecutorService executorService = Executors.newCachedThreadPool(); + public static Dex2OatService getDex2OatService() { + return dex2OatService; + } + public static ExecutorService getExecutorService() { return executorService; } @@ -98,6 +104,11 @@ public class ServiceManager { logcatService = new LogcatService(); logcatService.start(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + dex2OatService = new Dex2OatService(); + dex2OatService.start(); + } + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); Looper.prepareMainLooper(); diff --git a/dex2oat/.gitignore b/dex2oat/.gitignore new file mode 100644 index 00000000..9ff2a377 --- /dev/null +++ b/dex2oat/.gitignore @@ -0,0 +1,2 @@ +/build +/.cxx diff --git a/dex2oat/build.gradle.kts b/dex2oat/build.gradle.kts new file mode 100644 index 00000000..0309cece --- /dev/null +++ b/dex2oat/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * 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 . + * + * Copyright (C) 2022 LSPosed Contributors + */ + +plugins { + id("com.android.library") +} + +android { + namespace = "org.lsposed.dex2oat" + + buildFeatures { + androidResources = false + buildConfig = false + prefab = true + } + + defaultConfig { + minSdk = 29 + } + + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + } + } +} diff --git a/dex2oat/src/main/cpp/CMakeLists.txt b/dex2oat/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..b63eaad3 --- /dev/null +++ b/dex2oat/src/main/cpp/CMakeLists.txt @@ -0,0 +1,19 @@ +project(dex2oat) +cmake_minimum_required(VERSION 3.4.1) + +add_subdirectory(${EXTERNAL_ROOT} external) + +add_executable(dex2oat dex2oat.c) + +target_link_libraries(dex2oat cxx log) + +if (DEFINED DEBUG_SYMBOLS_PATH) + message(STATUS "Debug symbols will be placed at ${DEBUG_SYMBOLS_PATH}") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI} + COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $ + ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug + COMMAND ${CMAKE_STRIP} --strip-all $ + COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug + $) +endif() diff --git a/dex2oat/src/main/cpp/dex2oat.c b/dex2oat/src/main/cpp/dex2oat.c new file mode 100644 index 00000000..121dc68c --- /dev/null +++ b/dex2oat/src/main/cpp/dex2oat.c @@ -0,0 +1,114 @@ +/* + * 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 . + * + * Copyright (C) 2022 LSPosed Contributors + */ + +// +// Created by Nullptr on 2022/4/1. +// + +#include +#include +#include +#include +#include +#include + +#include "logging.h" + +#if defined(__LP64__) +# define LP_SELECT(lp32, lp64) lp64 +#else +# define LP_SELECT(lp32, lp64) lp32 +#endif + +char kTmpDir[] = "placeholder_/dev/0123456789abcdef"; + +static ssize_t xrecvmsg(int sockfd, struct msghdr *msg, int flags) { + int rec = recvmsg(sockfd, msg, flags); + if (rec < 0) { + PLOGE("recvmsg"); + } + return rec; +} + +static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) { + struct iovec iov = { + .iov_base = &cnt, + .iov_len = sizeof(cnt), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = bufsz + }; + + xrecvmsg(sockfd, &msg, MSG_WAITALL); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + if (msg.msg_controllen != bufsz || + cmsg == NULL || + cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { + return NULL; + } + + return CMSG_DATA(cmsg); +} + +static void write_int(int fd, int val) { + if (fd < 0) return; + write(fd, &val, sizeof(val)); +} + +static int recv_fd(int sockfd) { + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); + if (data == NULL) + return -1; + + int result; + memcpy(&result, data, sizeof(int)); + return result; +} + +int main(int argc, char **argv) { + LOGI("dex2oat wrapper"); + struct sockaddr_un sock; + sock.sun_family = AF_UNIX; + snprintf(sock.sun_path, sizeof(sock.sun_path), "%s/dex2oat.sock", kTmpDir + 12); + int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (-1 == connect(sock_fd, (struct sockaddr *) &sock, sizeof(sock))) { + PLOGE("failed to connect to %s", sock.sun_path); + return 1; + } + write_int(sock_fd, LP_SELECT(32, 64)); + int stock_fd = recv_fd(sock_fd); + close(sock_fd); + LOGD("sock: %s %d", sock.sun_path, stock_fd); + + const char *new_argv[argc + 2]; + for (int i = 0; i < argc; i++) new_argv[i] = argv[i]; + new_argv[argc] = "--inline-max-code-units=0"; + new_argv[argc + 1] = NULL; + fexecve(stock_fd, (char **) new_argv, environ); + PLOGE("fexecve failed"); + return 2; +} diff --git a/dex2oat/src/main/cpp/logging.h b/dex2oat/src/main/cpp/logging.h new file mode 100644 index 00000000..cb810dc7 --- /dev/null +++ b/dex2oat/src/main/cpp/logging.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#ifndef LOG_TAG +#define LOG_TAG "dex2oat-wrapper" +#endif + +#ifdef LOG_DISABLED +#define LOGD(...) +#define LOGV(...) +#define LOGI(...) +#define LOGW(...) +#define LOGE(...) +#else +#ifndef NDEBUG +#define LOGD(fmt, ...) \ + __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ + "%s:%d#%s" \ + ": " fmt, \ + __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) +#define LOGV(fmt, ...) \ + __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \ + "%s:%d#%s" \ + ": " fmt, \ + __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) +#else +#define LOGD(...) +#define LOGV(...) +#endif +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#endif diff --git a/magisk-loader/build.gradle.kts b/magisk-loader/build.gradle.kts index 85e1c0ed..bc48d1ba 100644 --- a/magisk-loader/build.gradle.kts +++ b/magisk-loader/build.gradle.kts @@ -143,7 +143,8 @@ fun afterEval() = android.applicationVariants.forEach { variant -> dependsOn( "assemble$variantCapped", ":app:package$buildTypeCapped", - ":daemon:package$buildTypeCapped" + ":daemon:package$buildTypeCapped", + ":dex2oat:merge${buildTypeCapped}NativeLibs" ) into(magiskDir) from("${rootProject.projectDir}/README.md") @@ -205,6 +206,11 @@ fun afterEval() = android.applicationVariants.forEach { variant -> include("**/libdaemon.so") } } + into("bin") { + from("${project(":dex2oat").buildDir}/intermediates/cmake/$buildTypeLowered/obj") { + include("**/dex2oat") + } + } val dexOutPath = if (buildTypeLowered == "release") "$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else "$buildDir/intermediates/dex/$variantCapped/mergeDex$variantCapped" diff --git a/magisk-loader/magisk_module/customize.sh b/magisk-loader/magisk_module/customize.sh index 42a0ad11..bec3cc6b 100644 --- a/magisk-loader/magisk_module/customize.sh +++ b/magisk-loader/magisk_module/customize.sh @@ -79,7 +79,6 @@ fi ui_print "- Extracting module files" extract "$ZIPFILE" 'module.prop' "$MODPATH" -extract "$ZIPFILE" 'system.prop' "$MODPATH" extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH" extract "$ZIPFILE" 'uninstall.sh' "$MODPATH" @@ -89,6 +88,31 @@ extract "$ZIPFILE" 'daemon' "$MODPATH" rm -f /data/adb/lspd/manager.apk extract "$ZIPFILE" 'manager.apk' '/data/adb/lspd' +if [ "$API" -ge 29 ]; then + ui_print "- Extracting dex2oat binaries" + mkdir "$MODPATH/bin" + + if [ "$ARCH" = "arm" ] || [ "$ARCH" = "arm64" ]; then + extract "$ZIPFILE" "bin/armeabi-v7a/dex2oat" "$MODPATH/bin" true + mv "$MODPATH/bin/dex2oat" "$MODPATH/bin/dex2oat32" + + if [ "$IS64BIT" = true ]; then + extract "$ZIPFILE" "bin/arm64-v8a/dex2oat" "$MODPATH/bin" true + mv "$MODPATH/bin/dex2oat" "$MODPATH/bin/dex2oat64" + fi + elif [ "$ARCH" == "x86" ] || [ "$ARCH" == "x64" ]; then + extract "$ZIPFILE" "bin/x86/dex2oat" "$MODPATH/bin" true + mv "$MODPATH/bin/dex2oat" "$MODPATH/bin/dex2oat32" + + if [ "$IS64BIT" = true ]; then + extract "$ZIPFILE" "bin/x86_64/dex2oat" "$MODPATH/bin" true + mv "$MODPATH/bin/dex2oat" "$MODPATH/bin/dex2oat64" + fi + fi +else + extract "$ZIPFILE" 'system.prop' "$MODPATH" +fi + ui_print "- Extracting daemon libraries" if [ "$ARCH" = "arm" ] ; then extract "$ZIPFILE" 'lib/armeabi-v7a/libdaemon.so' "$MODPATH" true @@ -161,6 +185,7 @@ elif [ "$FLAVOR" == "riru" ]; then fi set_perm_recursive "$MODPATH" 0 0 0755 0644 +set_perm_recursive "$MODPATH/bin" 0 0 0755 0755 u:object_r:dex2oat_exec:s0 chmod 0744 "$MODPATH/daemon" if [ "$(grep_prop ro.maple.enable)" == "1" ] && [ "$FLAVOR" == "zygisk" ]; then diff --git a/magisk-loader/magisk_module/daemon b/magisk-loader/magisk_module/daemon index 57f12a10..f7cf399e 100644 --- a/magisk-loader/magisk_module/daemon +++ b/magisk-loader/magisk_module/daemon @@ -1,6 +1,7 @@ #!/system/bin/sh dir=${0%/*} +magiskPath=$(magisk --path) tmpLspdApk="/data/local/tmp/daemon.apk" debug=@DEBUG@ flavor=@FLAVOR@ @@ -38,5 +39,16 @@ if [ ! -S "/dev/socket/zygote" ]; then done fi $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 exec /system/bin/app_process $java_options /system/bin --nice-name=lspd org.lsposed.lspd.Main "$@" >/dev/null 2>&1 diff --git a/magisk-loader/magisk_module/post-fs-data.sh b/magisk-loader/magisk_module/post-fs-data.sh index 8627d068..92fce217 100644 --- a/magisk-loader/magisk_module/post-fs-data.sh +++ b/magisk-loader/magisk_module/post-fs-data.sh @@ -18,7 +18,23 @@ # MODDIR=${0%/*} +MODNAME=${MODDIR##*/} +MAGISK_PATH=$(magisk --path) rm -f "/data/local/tmp/daemon.apk" cd "$MODDIR" + +if [ "$(getprop ro.build.version.sdk)" -ge 29 ]; then + TMP=$($RANDOM | md5sum | head -c 16) + while [ -d "/dev/$TMP" ]; do + TMP=$($RANDOM | md5sum | head -c 16) + done + mkdir "/dev/$TMP" + echo "/dev/$TMP" > "/data/adb/lspd/dev_path" + sed -i "s/placeholder_\/dev\/................/placeholder_\/dev\/$TMP/" "$MODDIR/bin/dex2oat32" + sed -i "s/placeholder_\/dev\/................/placeholder_\/dev\/$TMP/" "$MODDIR/bin/dex2oat64" + 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 &" diff --git a/settings.gradle.kts b/settings.gradle.kts index ad8ab6d4..31fab6d7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,7 @@ include( ":app", ":core", ":daemon", + ":dex2oat", ":hiddenapi:stubs", ":hiddenapi:bridge", ":magisk-loader",