diff --git a/.gitmodules b/.gitmodules index 191ffd656e9f..4473536d4dfa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,9 +22,6 @@ [submodule "zlib"] path = native/src/external/zlib url = https://android.googlesource.com/platform/external/zlib -[submodule "parallel-hashmap"] - path = native/src/external/parallel-hashmap - url = https://github.com/greg7mdp/parallel-hashmap.git [submodule "zopfli"] path = native/src/external/zopfli url = https://github.com/google/zopfli.git diff --git a/README.MD b/README.MD index 0f2bff1005fe..cb4bfcd34044 100644 --- a/README.MD +++ b/README.MD @@ -19,8 +19,8 @@ Some highlight features: [Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads. -[![](https://img.shields.io/badge/Magisk-v26.3-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.3) -[![](https://img.shields.io/badge/Magisk%20Beta-v26.3-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.3) +[![](https://img.shields.io/badge/Magisk-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4) +[![](https://img.shields.io/badge/Magisk%20Beta-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4) [![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk) [![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 24d2bdc2fd9b..dd7ad5c575dd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -34,7 +34,7 @@ Zorla şifrelemeyi koru AVB 2.0/dm-verity\'yi koruyun - Önyükleme görüntüsünde vbmeta yaması + Önyükleme görüntüsünde vbmeta yamasını uygula Kurtarma Modu Seçenekler Yöntem @@ -55,24 +55,24 @@ Bir uygulama bir Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor Reddet Hemen - İzin - Cihazınıza tam erişim sağlar.\nEmin değilseniz reddedin! + İzin ver + Cihazınıza tam erişim sağlar.\nEğer emin değilseniz reddedin! Daima Bir kere 10 dakika 20 dakika 30 dakika 60 dakika - %1$s Süper Kullanıcı hakları verildi - %1$s Süper Kullanıcı hakları reddedildi - %1$s Süper Kullanıcı hakları verildi - %1$s Süper Kullanıcı hakları reddedildi - %1$s bildirimleri etkinleştirildi - %1$s bildirimleri devre dışı bırakıldı - %1$s günlüğü etkinleştirildi - %1$s günlüğü devre dışı bırakıldı + %1$s uygulamasının Süper Kullanıcı izni verildi + %1$s uygulamasının Süper Kullanıcı izni reddedildi + %1$s uygulamasının Süper Kullanıcı izni verildi + %1$s uygulamasının Süper Kullanıcı izni reddedildi + %1$s uygulamasının bildirimleri etkinleştirildi + %1$s uygulamasının bildirimleri devre dışı bırakıldı + %1$s uygulamasının günlüğü etkinleştirildi + %1$s uygulamasının günlüğü devre dışı bırakıldı İptal et? - %1$s Süper Kullanıcı haklarını iptal etmeyi onaylayın + %1$s uygulamasının Süper Kullanıcı haklarını iptal etmeyi onaylayın Tost Hiçbiri @@ -99,11 +99,11 @@ (Bilgi verilmedi) - Yumuşak yeniden başlatma - Kurtarma için Yeniden Başlatın - Bootloader için Yeniden Başlatın - İndirmek için Yeniden Başlatın - EDL için Yeniden Başlatın + Yazılımsal olarak yeniden başlat + Kurtarma modunda Yeniden Başlat + Önyükleyici modunda Yeniden Başlat + Download modu için Yeniden Başlat + EDL modunda Yeniden Başlat %1$s / %2$s Kaldır Geri yükle @@ -119,9 +119,9 @@ Tema Modu Tarzınıza en uygun modu seçin! - Daima Açık + Her zaman Açık Sistemi Takip Et - Daima Koyu + Her zaman Koyu İndirme yolu Dosyalar %1$s konumuna kaydedilecek Magisk uygulamasını gizleyin @@ -131,7 +131,7 @@ Dil (Sistem Varsayılanı) Güncellemeleri Kontrol Et - Arka planda güncellemeleri periyodik olarak kontrol edin + Arka planda güncellemeleri düzenli olarak kontrol edin Güncelleme Kanalı Stabil Beta @@ -165,7 +165,7 @@ Süper Kullanıcı Bildirimi Yükseltmeden sonra yeniden kimlik doğrulaması yapın Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar isteyin - Sahte Ekran Koruması + Sahte Ekran (Tapjacking) Koruması Süper Kullanıcı bilgi istemi iletişim kutusu, herhangi bir başka pencere veya yer paylaşımı tarafından engellendiğinde girişe yanıt vermeyecektir. Özelleştir Uygulamayı gizledikten sonra adın ve simgenin tanınmasının zor olması durumunda ana ekrana güzel bir kısayol ekleyin @@ -216,7 +216,7 @@ Geri yükleme tamamlandı! Stok yedeği mevcut değil! Kurulum başarısız oldu - Ek Kurulum Gerektirir + Ek Kurulum Gerekiyor Magisk\'in düzgün çalışması için cihazınızın ek kuruluma ihtiyacı var. Devam etmek ve yeniden başlatmak istiyor musunuz? Cihazınızın düzgün çalışması için Magisk\'in yeniden yüklenmeye ihtiyacı var. Lütfen Magisk\'i uygulama içinde yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamıyor. Ortam kurulumu çalıştırılıyor… @@ -224,18 +224,18 @@ Desteklenmeyen Magisk Sürümü Uygulamanın bu sürümü, %1$s\'den daha düşük Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk kurulu değilmiş gibi davranacak, lütfen Magisk\'i mümkün olan en kısa sürede yükseltin. Anormal Durum - Bu uygulamayı bir sistem uygulaması olarak çalıştırmak desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri döndürün. - Magisk\'ten olmayan bir \"su\" ikili dosyası algılandı. Lütfen rakip kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin. + Bu uygulamayı bir sistem uygulaması olarak çalıştırma desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri döndürün. + Magisk\'ten olmayan bir \"su\" ikili dosyası algılandı. Lütfen başka kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin. Magisk, harici depolama birimine kurulur. Lütfen uygulamayı dahili depolamaya taşıyın. Kök kaybolduğu için gizli Magisk uygulaması çalışmaya devam edemiyor. Lütfen orijinal APK\'yı geri yükleyin. @string/settings_restore_app_title - Bu işlevi etkinleştirmek için depolama izni verin - Bu işlevi etkinleştirmek için bildirim izni verin - Bu işlevi etkinleştirmek için "bilinmeyen uygulamaları yükle"ye izin verin + Bu işlevi etkinleştirmek için depolama izni veriniz. + Bu işlevi etkinleştirmek için bildirim izni veriniz. + Bu işlevi etkinleştirmek için "Bilinmeyen uygulamaları yükle" ayarına izin veriniz. Ana ekrana kısayol ekle Bu uygulamayı gizledikten sonra adını ve simgesini tanımak zorlaşabilir. Ana ekrana hoş bir kısayol eklemek ister misiniz? Bu eylemi gerçekleştirecek uygulama bulunamadı Değişiklikleri uygulamak için yeniden başlatın - Bu, gizli uygulamayı orijinal uygulamaya geri yükleyecektir. Gerçekten bunu yapmak istiyor musun? + Bu işlem, gizli uygulamayı orijinal uygulama ile değiştirecektir. Bu işlemi yapmak istediğinizden emin misiniz? diff --git a/build.py b/build.py index e34ec66676eb..55af084e05af 100755 --- a/build.py +++ b/build.py @@ -246,7 +246,7 @@ def run_ndk_build(flags): error("Build binary failed!") os.chdir("..") for arch in archs: - for tgt in support_targets + ["libinit-ld.so", "libzygisk-ld.so"]: + for tgt in support_targets + ["libinit-ld.so"]: source = op.join("native", "libs", arch, tgt) target = op.join("native", "out", arch, tgt) mv(source, target) @@ -342,9 +342,6 @@ def dump_bin_header(args): preload = op.join("native", "out", arch, "libinit-ld.so") with open(preload, "rb") as src: text = binary_dump(src, "init_ld_xz") - preload = op.join("native", "out", arch, "libzygisk-ld.so") - with open(preload, "rb") as src: - text += binary_dump(src, "zygisk_ld", compressor=lambda x: x) write_if_diff(op.join(native_gen_path, f"{arch}_binaries.h"), text) @@ -395,8 +392,9 @@ def build_binary(args): flag = "" clean = False - if "magisk" in args.target or "magiskinit" in args.target: - flag += " B_PRELOAD=1" + if "magisk" in args.target: + flag += " B_MAGISK=1" + clean = True if "magiskpolicy" in args.target: flag += " B_POLICY=1" @@ -417,14 +415,10 @@ def build_binary(args): if flag: run_ndk_build(flag) - # magiskinit and magisk embeds preload.so + # magiskinit embeds preload.so flag = "" - if "magisk" in args.target: - flag += " B_MAGISK=1" - clean = True - if "magiskinit" in args.target: flag += " B_INIT=1" diff --git a/docs/changes.md b/docs/changes.md index a9125d7fe25a..039cea5f203c 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -1,5 +1,16 @@ # Magisk Changelog +### v26.4 + +- [MagiskBoot] Don't pad zeros if signed boot image is larger +- [MagiskPolicy] Fix `genfscon` and `filename_trans` +- [MagiskPolicy] Fix bug in `libsepol` +- [Zygisk] Fix and simplify file descriptor sanitization logic +- [App] Prevent OOM when patching AP tarfiles +- [App] Fix bug in device configuration detection +- [Daemon] Fix certificate parsing of APKs +- [General] Fix logging errors from C++ code being ignored + ### v26.3 - [General] Fix device information detection script diff --git a/docs/releases/26400.md b/docs/releases/26400.md new file mode 100644 index 000000000000..d0f318e2466d --- /dev/null +++ b/docs/releases/26400.md @@ -0,0 +1,12 @@ +## 2023.11.5 Magisk v26.4 + +- [MagiskBoot] Don't pad zeros if signed boot image is larger +- [MagiskPolicy] Fix `genfscon` and `filename_trans` +- [MagiskPolicy] Fix bug in `libsepol` +- [Zygisk] Fix and simplify file descriptor sanitization logic +- [App] Prevent OOM when patching AP tarfiles +- [App] Fix bug in device configuration detection +- [Daemon] Fix certificate parsing of APKs +- [General] Fix logging errors from C++ code being ignored + +### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html) diff --git a/docs/releases/index.md b/docs/releases/index.md index ce973b9f86c5..6a208d6228df 100644 --- a/docs/releases/index.md +++ b/docs/releases/index.md @@ -1,5 +1,6 @@ # Release Notes +- [v26.4](26400.md) - [v26.3](26300.md) - [v26.2](26200.md) - [v26.1](26100.md) diff --git a/gradle.properties b/gradle.properties index 73ee649d7d98..74cdf51720c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,5 +27,5 @@ android.nonFinalResIds=false # Magisk magisk.stubVersion=38 -magisk.versionCode=26302 +magisk.versionCode=26401 magisk.ondkVersion=r26.1 diff --git a/native/src/Android-rs.mk b/native/src/Android-rs.mk index 2a0027335bdf..cb04a076f747 100644 --- a/native/src/Android-rs.mk +++ b/native/src/Android-rs.mk @@ -8,6 +8,7 @@ LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH))) include $(CLEAR_VARS) LOCAL_MODULE := magisk-rs +LOCAL_EXPORT_C_INCLUDES := src/core/include LOCAL_SRC_FILES := $(LIBRARY_PATH) include $(PREBUILT_STATIC_LIBRARY) endif diff --git a/native/src/Android.mk b/native/src/Android.mk index 1bb0c2d995b1..0a6b82942f1f 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -11,7 +11,6 @@ LOCAL_MODULE := magisk LOCAL_STATIC_LIBRARIES := \ libbase \ libsystemproperties \ - libphmap \ liblsplt \ libmagisk-rs @@ -27,20 +26,19 @@ LOCAL_SRC_FILES := \ core/selinux.cpp \ core/module.cpp \ core/thread.cpp \ - core/resetprop/resetprop.cpp \ core/core-rs.cpp \ + core/resetprop/resetprop.cpp \ core/su/su.cpp \ core/su/connect.cpp \ core/su/pts.cpp \ core/su/su_daemon.cpp \ - zygisk/entry.cpp \ - zygisk/main.cpp \ - zygisk/utils.cpp \ - zygisk/hook.cpp \ - zygisk/memory.cpp \ - zygisk/deny/cli.cpp \ - zygisk/deny/utils.cpp \ - zygisk/deny/revert.cpp + core/zygisk/entry.cpp \ + core/zygisk/main.cpp \ + core/zygisk/module.cpp \ + core/zygisk/hook.cpp \ + core/deny/cli.cpp \ + core/deny/utils.cpp \ + core/deny/revert.cpp LOCAL_LDLIBS := -llog LOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt @@ -57,12 +55,6 @@ LOCAL_SRC_FILES := init/preload.c LOCAL_STRIP_MODE := --strip-all include $(BUILD_SHARED_LIBRARY) -include $(CLEAR_VARS) -LOCAL_MODULE := zygisk-ld -LOCAL_SRC_FILES := zygisk/loader.c -LOCAL_STRIP_MODE := --strip-all -include $(BUILD_SHARED_LIBRARY) - endif ifdef B_INIT diff --git a/native/src/base/include/base.hpp b/native/src/base/include/base.hpp index 7ca70dc5f530..01d350a5821e 100644 --- a/native/src/base/include/base.hpp +++ b/native/src/base/include/base.hpp @@ -4,7 +4,6 @@ #include "../files.hpp" #include "../misc.hpp" #include "../logging.hpp" -#include "../missing.hpp" #include "../base-rs.hpp" using rust::xpipe2; diff --git a/native/src/base/misc.hpp b/native/src/base/misc.hpp index ab94bc6a540b..476395fe9933 100644 --- a/native/src/base/misc.hpp +++ b/native/src/base/misc.hpp @@ -262,9 +262,9 @@ std::vector split(std::string_view s, std::string_view delims); std::vector split_view(std::string_view, std::string_view delims); // Similar to vsnprintf, but the return value is the written number of bytes -int vssprintf(char *dest, size_t size, const char *fmt, va_list ap); +__printflike(3, 0) int vssprintf(char *dest, size_t size, const char *fmt, va_list ap); // Similar to snprintf, but the return value is the written number of bytes -int ssprintf(char *dest, size_t size, const char *fmt, ...); +__printflike(3, 4) int ssprintf(char *dest, size_t size, const char *fmt, ...); // This is not actually the strscpy from the Linux kernel. // Silently truncates, and returns the number of bytes written. extern "C" size_t strscpy(char *dest, const char *src, size_t size); diff --git a/native/src/base/missing.hpp b/native/src/base/missing.hpp deleted file mode 100644 index c33f3138c593..000000000000 --- a/native/src/base/missing.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -static inline int fexecve(int fd, char* const* argv, char* const* envp) { - syscall(__NR_execveat, fd, "", argv, envp, AT_EMPTY_PATH); - if (errno == ENOSYS) { - char buf[256]; - ssprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); - execve(buf, argv, envp); - } - return -1; -} diff --git a/native/src/base/xwrap.hpp b/native/src/base/xwrap.hpp index ce7a6dda8778..917e300dc550 100644 --- a/native/src/base/xwrap.hpp +++ b/native/src/base/xwrap.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/native/src/core/applet_stub.cpp b/native/src/core/applet_stub.cpp index 52a6f32dfff6..291abb962a16 100644 --- a/native/src/core/applet_stub.cpp +++ b/native/src/core/applet_stub.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/native/src/core/applets.cpp b/native/src/core/applets.cpp index a81712be0931..0b6d0b537039 100644 --- a/native/src/core/applets.cpp +++ b/native/src/core/applets.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -31,11 +31,6 @@ int main(int argc, char *argv[]) { string_view argv0 = basename(argv[0]); - // app_process is actually not an applet - if (argv0.starts_with("app_process")) { - return app_process_main(argc, argv); - } - umask(0); if (argv[0][0] == '\0') { diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp index 8457da38343e..45c1722f94cd 100644 --- a/native/src/core/bootstages.cpp +++ b/native/src/core/bootstages.cpp @@ -6,14 +6,12 @@ #include #include -#include +#include #include #include -#include +#include #include -#include "core.hpp" - using namespace std; // Boot stage state @@ -45,21 +43,24 @@ static bool mount_mirror(const std::string_view from, const std::string_view to) static void mount_mirrors() { LOGI("* Mounting mirrors\n"); auto self_mount_info = parse_mount_info("self"); + char path[64]; // Bind remount module root to clear nosuid if (access(SECURE_DIR, F_OK) == 0 || SDK_INT < 24) { - auto dest = MAGISKTMP + "/" MODULEMNT; + ssprintf(path, sizeof(path), "%s/" MODULEMNT, get_magisk_tmp()); xmkdir(SECURE_DIR, 0700); xmkdir(MODULEROOT, 0755); - xmkdir(dest.data(), 0755); - xmount(MODULEROOT, dest.data(), nullptr, MS_BIND, nullptr); - xmount(nullptr, dest.data(), nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr); - xmount(nullptr, dest.data(), nullptr, MS_PRIVATE, nullptr); + xmkdir(path, 0755); + xmount(MODULEROOT, path, nullptr, MS_BIND, nullptr); + xmount(nullptr, path, nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr); + xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr); chmod(SECURE_DIR, 0700); } // Check and mount preinit mirror - if (struct stat st{}; stat((MAGISKTMP + "/" PREINITDEV).data(), &st) == 0 && (st.st_mode & S_IFBLK)) { + char dev_path[64]; + ssprintf(dev_path, sizeof(dev_path), "%s/" PREINITDEV, get_magisk_tmp()); + if (struct stat st{}; stat(dev_path, &st) == 0 && S_ISBLK(st.st_mode)) { // DO NOT mount the block device directly, as we do not know the flags and configs // to properly mount the partition; mounting block devices directly as rw could cause // crashes if the filesystem driver is crap (e.g. some broken F2FS drivers). @@ -67,6 +68,7 @@ static void mount_mirrors() { // mount point mounting our desired partition, and then bind mount the target folder. dev_t preinit_dev = st.st_rdev; bool mounted = false; + ssprintf(path, sizeof(path), "%s/" PREINITMIRR, get_magisk_tmp()); for (const auto &info: self_mount_info) { if (info.root == "/" && info.device == preinit_dev) { auto flags = split_view(info.fs_option, ","); @@ -76,29 +78,28 @@ static void mount_mirrors() { if (!rw) continue; string preinit_dir = resolve_preinit_dir(info.target.data()); xmkdir(preinit_dir.data(), 0700); - auto mirror_dir = MAGISKTMP + "/" PREINITMIRR; - if ((mounted = mount_mirror(preinit_dir, mirror_dir))) { - xmount(nullptr, mirror_dir.data(), nullptr, MS_UNBINDABLE, nullptr); + if ((mounted = mount_mirror(preinit_dir, path))) { + xmount(nullptr, path, nullptr, MS_UNBINDABLE, nullptr); break; } } } if (!mounted) { LOGW("preinit mirror not mounted %u:%u\n", major(preinit_dev), minor(preinit_dev)); - unlink((MAGISKTMP + "/" PREINITDEV).data()); + unlink(dev_path); } } // Prepare worker - auto worker_dir = MAGISKTMP + "/" WORKERDIR; - xmount("worker", worker_dir.data(), "tmpfs", 0, "mode=755"); - xmount(nullptr, worker_dir.data(), nullptr, MS_PRIVATE, nullptr); + ssprintf(path, sizeof(path), "%s/" WORKERDIR, get_magisk_tmp()); + xmount("worker", path, "tmpfs", 0, "mode=755"); + xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr); // Recursively bind mount / to mirror dir - if (auto mirror_dir = MAGISKTMP + "/" MIRRDIR; !mount_mirror("/", mirror_dir)) { + if (auto mirror_dir = get_magisk_tmp() + "/"s MIRRDIR; !mount_mirror("/", mirror_dir)) { LOGI("fallback to mount subtree\n"); // rootfs may fail, fallback to bind mount each mount point - set> mounted_dirs {{ MAGISKTMP }}; + set> mounted_dirs {{ get_magisk_tmp() }}; for (const auto &info: self_mount_info) { if (info.type == "rootfs"sv) continue; // the greatest mount point that less than info.target, which is possibly a parent @@ -219,8 +220,6 @@ static bool magisk_env() { LOGI("* Initializing Magisk environment\n"); preserve_stub_apk(); - string pkg; - get_manager(0, &pkg); // Directories in /data/adb xmkdir(DATABIN, 0755); @@ -231,13 +230,13 @@ static bool magisk_env() { if (access(DATABIN "/busybox", X_OK)) return false; - sprintf(buf, "%s/" BBPATH "/busybox", MAGISKTMP.data()); + ssprintf(buf, sizeof(buf), "%s/" BBPATH "/busybox", get_magisk_tmp()); mkdir(dirname(buf), 0755); cp_afc(DATABIN "/busybox", buf); exec_command_async(buf, "--install", "-s", dirname(buf)); if (access(DATABIN "/magiskpolicy", X_OK) == 0) { - sprintf(buf, "%s/magiskpolicy", MAGISKTMP.data()); + ssprintf(buf, sizeof(buf), "%s/magiskpolicy", get_magisk_tmp()); cp_afc(DATABIN "/magiskpolicy", buf); } @@ -415,6 +414,8 @@ static void boot_complete() { // Ensure manager exists check_pkg_refresh(); get_manager(0, nullptr, true); + + reset_zygisk(true); } void boot_stage_handler(int client, int code) { diff --git a/native/src/core/core.hpp b/native/src/core/core.hpp deleted file mode 100644 index 7f5148e5bf13..000000000000 --- a/native/src/core/core.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -#include "core-rs.hpp" -#include "resetprop/resetprop.hpp" - -extern bool RECOVERY_MODE; -extern std::atomic pkg_xml_ino; - -std::string find_preinit_device(); -void unlock_blocks(); -void reboot(); - -// Module stuffs -void handle_modules(); -void load_modules(); -void disable_modules(); -void remove_modules(); -void exec_module_scripts(const char *stage); - -// Scripting -void exec_script(const char *script); -void exec_common_scripts(const char *stage); -void exec_module_scripts(const char *stage, const std::vector &modules); -void install_apk(const char *apk); -void uninstall_pkg(const char *pkg); -void clear_pkg(const char *pkg, int user_id); -[[noreturn]] void install_module(const char *file); diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index 2323adba5cd9..3404e5deb37a 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -3,19 +3,16 @@ #include #include -#include +#include #include -#include +#include #include #include #include -#include "core.hpp" - using namespace std; int SDK_INT = -1; -string MAGISKTMP; bool RECOVERY_MODE = false; @@ -142,10 +139,11 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { su_daemon_handler(client, &cred); break; case MainRequest::ZYGOTE_RESTART: - close(client); LOGI("** zygote restarted\n"); pkg_xml_ino = 0; prune_su_access(); + reset_zygisk(false); + close(client); break; case MainRequest::SQLITE_CMD: exec_sql(client); @@ -159,7 +157,6 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { break; } case MainRequest::ZYGISK: - case MainRequest::ZYGISK_PASSTHROUGH: zygisk_handler(client, &cred); break; default: @@ -182,11 +179,24 @@ static void handle_request_sync(int client, int code) { case MainRequest::START_DAEMON: rust::get_magiskd().setup_logfile(); break; - case MainRequest::STOP_DAEMON: + case MainRequest::STOP_DAEMON: { + // Unmount all overlays denylist_handler(-1, nullptr); + + // Restore native bridge property + auto nb = get_prop(NBPROP); + auto len = sizeof(ZYGISKLDR) - 1; + if (nb == ZYGISKLDR) { + set_prop(NBPROP, "0"); + } else if (nb.size() > len) { + set_prop(NBPROP, nb.data() + len); + } + write_int(client, 0); + // Terminate the daemon! exit(0); + } default: __builtin_unreachable(); } @@ -328,9 +338,6 @@ static void daemon_entry() { } // Get self stat - char buf[64]; - xreadlink("/proc/self/exe", buf, sizeof(buf)); - MAGISKTMP = dirname(buf); xstat("/proc/self/exe", &self_st); // Get API level @@ -353,9 +360,11 @@ static void daemon_entry() { restore_tmpcon(); // Cleanups - auto mount_list = MAGISKTMP + "/" ROOTMNT; - if (access(mount_list.data(), F_OK) == 0) { - file_readline(true, mount_list.data(), [](string_view line) -> bool { + const char *tmp = get_magisk_tmp(); + char path[64]; + ssprintf(path, sizeof(path), "%s/" ROOTMNT, tmp); + if (access(path, F_OK) == 0) { + file_readline(true, path, [](string_view line) -> bool { umount2(line.data(), MNT_DETACH); return true; }); @@ -364,11 +373,12 @@ static void daemon_entry() { xmount(nullptr, "/", nullptr, MS_REMOUNT | MS_RDONLY, nullptr); unsetenv("REMOUNT_ROOT"); } - rm_rf((MAGISKTMP + "/" ROOTOVL).data()); + ssprintf(path, sizeof(path), "%s/" ROOTOVL, tmp); + rm_rf(path); // Load config status - auto config = MAGISKTMP + "/" MAIN_CONFIG; - parse_prop_file(config.data(), [](auto key, auto val) -> bool { + ssprintf(path, sizeof(path), "%s/" MAIN_CONFIG, tmp); + parse_prop_file(path, [](auto key, auto val) -> bool { if (key == "RECOVERYMODE" && val == "true") RECOVERY_MODE = true; return true; @@ -376,22 +386,22 @@ static void daemon_entry() { // Use isolated devpts if kernel support if (access("/dev/pts/ptmx", F_OK) == 0) { - auto pts = MAGISKTMP + "/" SHELLPTS; - if (access(pts.data(), F_OK)) { - xmkdirs(pts.data(), 0755); - xmount("devpts", pts.data(), "devpts", - MS_NOSUID | MS_NOEXEC, "newinstance"); - auto ptmx = pts + "/ptmx"; - if (access(ptmx.data(), F_OK)) { - xumount(pts.data()); - rmdir(pts.data()); + ssprintf(path, sizeof(path), "%s/" SHELLPTS, tmp); + if (access(path, F_OK)) { + xmkdirs(path, 0755); + xmount("devpts", path, "devpts", MS_NOSUID | MS_NOEXEC, "newinstance"); + char ptmx[64]; + ssprintf(ptmx, sizeof(ptmx), "%s/ptmx", path); + if (access(ptmx, F_OK)) { + xumount(path); + rmdir(path); } } } fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); sockaddr_un addr = {.sun_family = AF_LOCAL}; - strcpy(addr.sun_path, (MAGISKTMP + "/" MAIN_SOCKET).data()); + ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp); unlink(addr.sun_path); if (xbind(fd, (sockaddr *) &addr, sizeof(addr))) exit(1); @@ -411,27 +421,25 @@ static void daemon_entry() { poll_loop(); } -string find_magisk_tmp() { - if (access("/debug_ramdisk/" INTLROOT, F_OK) == 0) { - return "/debug_ramdisk"; - } - if (access("/sbin/" INTLROOT, F_OK) == 0) { - return "/sbin"; - } - // Fallback to lookup from mountinfo for manual mount, e.g. avd - for (const auto &mount: parse_mount_info("self")) { - if (mount.source == "magisk" && mount.root == "/") { - return mount.target; +const char *get_magisk_tmp() { + static const char *path = nullptr; + if (path == nullptr) { + if (access("/debug_ramdisk/" INTLROOT, F_OK) == 0) { + path = "/debug_ramdisk"; + } else if (access("/sbin/" INTLROOT, F_OK) == 0) { + path = "/sbin"; + } else { + path = ""; } } - return ""; + return path; } int connect_daemon(int req, bool create) { int fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); sockaddr_un addr = {.sun_family = AF_LOCAL}; - string tmp = find_magisk_tmp(); - strcpy(addr.sun_path, (tmp + "/" MAIN_SOCKET).data()); + const char *tmp = get_magisk_tmp(); + ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp); if (connect(fd, (sockaddr *) &addr, sizeof(addr))) { if (!create || getuid() != AID_ROOT) { LOGE("No daemon is currently running!\n"); @@ -441,7 +449,7 @@ int connect_daemon(int req, bool create) { char buf[64]; xreadlink("/proc/self/exe", buf, sizeof(buf)); - if (tmp.empty() || !str_starts(buf, tmp)) { + if (tmp[0] == '\0' || !str_starts(buf, tmp)) { LOGE("Start daemon on magisk tmpfs\n"); close(fd); return -1; diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index 14bb6602edab..47cffb30fe23 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -1,22 +1,39 @@ -use std::cell::RefCell; use std::fs::File; use std::io; use std::sync::{Mutex, OnceLock}; -use crate::get_prop; use base::{cstr, Directory, ResultExt, Utf8CStr, Utf8CStrBuf, Utf8CStrBufRef, WalkResult}; -use crate::logging::{magisk_logging, zygisk_logging}; +use crate::get_prop; +use crate::logging::magisk_logging; // Global magiskd singleton pub static MAGISKD: OnceLock = OnceLock::new(); #[derive(Default)] pub struct MagiskD { - pub logd: Mutex>>, + pub logd: Mutex>, is_emulator: bool, } +impl MagiskD { + pub fn is_emulator(&self) -> bool { + self.is_emulator + } +} + +mod cxx_extern { + use base::libc::c_char; + + extern "C" { + pub fn get_magisk_tmp() -> *const c_char; + } +} + +pub fn get_magisk_tmp() -> &'static Utf8CStr { + unsafe { Utf8CStr::from_ptr(cxx_extern::get_magisk_tmp()).unwrap_unchecked() } +} + pub fn daemon_entry() { let mut qemu = get_prop(cstr!("ro.kernel.qemu"), false); if qemu.is_empty() { @@ -33,22 +50,10 @@ pub fn daemon_entry() { magisk_logging(); } -pub fn zygisk_entry() { - let magiskd = MagiskD::default(); - MAGISKD.set(magiskd).ok(); - zygisk_logging(); -} - pub fn get_magiskd() -> &'static MagiskD { unsafe { MAGISKD.get().unwrap_unchecked() } } -impl MagiskD { - pub fn is_emulator(&self) -> bool { - self.is_emulator - } -} - pub fn find_apk_path(pkg: &[u8], data: &mut [u8]) -> usize { use WalkResult::*; fn inner(pkg: &[u8], buf: &mut dyn Utf8CStrBuf) -> io::Result { diff --git a/native/src/core/db.cpp b/native/src/core/db.cpp index b55c931105fe..3c6f139592e8 100644 --- a/native/src/core/db.cpp +++ b/native/src/core/db.cpp @@ -2,12 +2,10 @@ #include #include -#include -#include -#include +#include #include - -#include "core.hpp" +#include +#include #define DB_VERSION 12 diff --git a/native/src/zygisk/deny/cli.cpp b/native/src/core/deny/cli.cpp similarity index 99% rename from native/src/zygisk/deny/cli.cpp rename to native/src/core/deny/cli.cpp index 0e9e7434f0a0..33d380cc894c 100644 --- a/native/src/zygisk/deny/cli.cpp +++ b/native/src/core/deny/cli.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include "deny.hpp" diff --git a/native/src/zygisk/deny/deny.hpp b/native/src/core/deny/deny.hpp similarity index 77% rename from native/src/zygisk/deny/deny.hpp rename to native/src/core/deny/deny.hpp index 656b1dcca675..557b92c4213e 100644 --- a/native/src/zygisk/deny/deny.hpp +++ b/native/src/core/deny/deny.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #define ISOLATED_MAGIC "isolated" @@ -44,9 +44,3 @@ int disable_deny(); int add_list(int client); int rm_list(int client); void ls_list(int client); - -// Utility functions -bool is_deny_target(int uid, std::string_view process); -void revert_unmount(); - -extern std::atomic denylist_enforced; diff --git a/native/src/zygisk/deny/revert.cpp b/native/src/core/deny/revert.cpp similarity index 96% rename from native/src/zygisk/deny/revert.cpp rename to native/src/core/deny/revert.cpp index 01cdcf01aa21..90dd206cabf2 100644 --- a/native/src/zygisk/deny/revert.cpp +++ b/native/src/core/deny/revert.cpp @@ -1,8 +1,9 @@ #include #include -#include +#include #include +#include #include "deny.hpp" diff --git a/native/src/zygisk/deny/utils.cpp b/native/src/core/deny/utils.cpp similarity index 99% rename from native/src/zygisk/deny/utils.cpp rename to native/src/core/deny/utils.cpp index 68c4b75ac577..63629d5d581e 100644 --- a/native/src/zygisk/deny/utils.cpp +++ b/native/src/core/deny/utils.cpp @@ -6,9 +6,10 @@ #include #include -#include +#include #include #include +#include #include "deny.hpp" diff --git a/native/src/include/daemon.hpp b/native/src/core/include/core.hpp similarity index 69% rename from native/src/include/daemon.hpp rename to native/src/core/include/core.hpp index d2292472e542..fb6efd872da0 100644 --- a/native/src/include/daemon.hpp +++ b/native/src/core/include/core.hpp @@ -7,8 +7,8 @@ #include #include -#include -#include "../core/core-rs.hpp" +#include "socket.hpp" +#include "../core-rs.hpp" #define AID_ROOT 0 #define AID_SHELL 2000 @@ -35,7 +35,6 @@ enum : int { SQLITE_CMD, REMOVE_MODULES, ZYGISK, - ZYGISK_PASSTHROUGH, _STAGE_BARRIER_, @@ -66,13 +65,16 @@ struct module_info { #endif }; +extern bool RECOVERY_MODE; extern bool zygisk_enabled; -extern int app_process_32; -extern int app_process_64; extern std::vector *module_list; -std::string find_magisk_tmp(); +void reset_zygisk(bool restore); +extern "C" const char *get_magisk_tmp(); int connect_daemon(int req, bool create = false); +std::string find_preinit_device(); +void unlock_blocks(); +void reboot(); // Poll control using poll_callback = void(*)(pollfd*); @@ -90,6 +92,7 @@ void su_daemon_handler(int client, const sock_cred *cred); void zygisk_handler(int client, const sock_cred *cred); // Package +extern std::atomic pkg_xml_ino; void preserve_stub_apk(); void check_pkg_refresh(); std::vector get_app_no_list(); @@ -98,7 +101,26 @@ std::vector get_app_no_list(); int get_manager(int user_id = 0, std::string *pkg = nullptr, bool install = false); void prune_su_access(); +// Module stuffs +void handle_modules(); +void load_modules(); +void disable_modules(); +void remove_modules(); +void exec_module_scripts(const char *stage); + +// Scripting +void exec_script(const char *script); +void exec_common_scripts(const char *stage); +void exec_module_scripts(const char *stage, const std::vector &modules); +void install_apk(const char *apk); +void uninstall_pkg(const char *pkg); +void clear_pkg(const char *pkg, int user_id); +[[noreturn]] void install_module(const char *file); + // Denylist extern std::atomic_flag skip_pkg_rescan; -void initialize_denylist(); +extern std::atomic denylist_enforced; int denylist_cli(int argc, char **argv); +void initialize_denylist(); +bool is_deny_target(int uid, std::string_view process); +void revert_unmount(); diff --git a/native/src/include/db.hpp b/native/src/core/include/db.hpp similarity index 100% rename from native/src/include/db.hpp rename to native/src/core/include/db.hpp diff --git a/native/src/core/resetprop/resetprop.hpp b/native/src/core/include/resetprop.hpp similarity index 91% rename from native/src/core/resetprop/resetprop.hpp rename to native/src/core/include/resetprop.hpp index 05b297dd7610..931f52500e11 100644 --- a/native/src/core/resetprop/resetprop.hpp +++ b/native/src/core/include/resetprop.hpp @@ -4,9 +4,6 @@ #include #include -#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ -#include - struct prop_cb { virtual void exec(const char *name, const char *value) = 0; }; diff --git a/native/src/include/selinux.hpp b/native/src/core/include/selinux.hpp similarity index 100% rename from native/src/include/selinux.hpp rename to native/src/core/include/selinux.hpp diff --git a/native/src/include/socket.hpp b/native/src/core/include/socket.hpp similarity index 100% rename from native/src/include/socket.hpp rename to native/src/core/include/socket.hpp diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 3e1310a5d756..55529096ba10 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -3,8 +3,10 @@ use base::Utf8CStr; use cert::read_certificate; -use daemon::{daemon_entry, find_apk_path, get_magiskd, zygisk_entry, MagiskD}; -use logging::{android_logging, magisk_logging, zygisk_logging}; +use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD}; +use logging::{ + android_logging, magisk_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging, +}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; mod cert; @@ -17,7 +19,7 @@ mod resetprop; #[cxx::bridge] pub mod ffi { extern "C++" { - include!("resetprop/resetprop.hpp"); + include!("include/resetprop.hpp"); #[cxx_name = "prop_cb"] type PropCb; @@ -30,6 +32,8 @@ pub mod ffi { fn android_logging(); fn magisk_logging(); fn zygisk_logging(); + fn zygisk_close_logd(); + fn zygisk_get_logd() -> i32; fn find_apk_path(pkg: &[u8], data: &mut [u8]) -> usize; fn read_certificate(fd: i32, version: i32) -> Vec; unsafe fn persist_get_prop(name: *const c_char, prop_cb: Pin<&mut PropCb>); @@ -41,7 +45,6 @@ pub mod ffi { #[namespace = "rust"] extern "Rust" { fn daemon_entry(); - fn zygisk_entry(); type MagiskD; fn get_magiskd() -> &'static MagiskD; diff --git a/native/src/core/logging.rs b/native/src/core/logging.rs index bac162033521..bca550afa38e 100644 --- a/native/src/core/logging.rs +++ b/native/src/core/logging.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::io::{IoSlice, Read, Write}; use std::os::fd::{AsRawFd, FromRawFd, RawFd}; use std::ptr::null_mut; +use std::sync::atomic::{AtomicI32, Ordering}; use std::{fs, io}; use bytemuck::{bytes_of, bytes_of_mut, write_zeroes, Pod, Zeroable}; @@ -12,15 +13,15 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::FromPrimitive; use base::libc::{ - clock_gettime, getpid, gettid, localtime_r, pipe2, pthread_sigmask, sigaddset, sigset_t, - sigtimedwait, timespec, tm, CLOCK_REALTIME, O_CLOEXEC, PIPE_BUF, SIGPIPE, SIG_BLOCK, + clock_gettime, getpid, gettid, localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait, + timespec, tm, CLOCK_REALTIME, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK, SIG_SETMASK, }; use base::*; -use crate::daemon::{MagiskD, MAGISKD}; +use crate::daemon::{get_magisk_tmp, MagiskD, MAGISKD}; use crate::logging::LogFile::{Actual, Buffer}; -use crate::LOGFILE; +use crate::{LOGFILE, LOG_PIPE}; #[allow(dead_code, non_camel_case_types)] #[derive(FromPrimitive, ToPrimitive)] @@ -42,8 +43,6 @@ type ThreadEntry = extern "C" fn(*mut c_void) -> *mut c_void; extern "C" { fn __android_log_write(prio: i32, tag: *const c_char, msg: *const c_char); fn strftime(buf: *mut c_char, len: usize, fmt: *const c_char, tm: *const tm) -> usize; - - fn zygisk_fetch_logd() -> RawFd; fn new_daemon_thread(entry: ThreadEntry, arg: *mut c_void); } @@ -58,9 +57,7 @@ fn level_to_prio(level: LogLevel) -> i32 { pub fn android_logging() { fn android_log_write(level: LogLevel, msg: &Utf8CStr) { - unsafe { - __android_log_write(level_to_prio(level), raw_cstr!("Magisk"), msg.as_ptr()); - } + write_android_log(level_to_prio(level), msg); } let logger = Logger { @@ -75,9 +72,7 @@ pub fn android_logging() { pub fn magisk_logging() { fn magisk_log_write(level: LogLevel, msg: &Utf8CStr) { - unsafe { - __android_log_write(level_to_prio(level), raw_cstr!("Magisk"), msg.as_ptr()); - } + write_android_log(level_to_prio(level), msg); magisk_log_to_pipe(level_to_prio(level), msg); } @@ -93,9 +88,7 @@ pub fn magisk_logging() { pub fn zygisk_logging() { fn zygisk_log_write(level: LogLevel, msg: &Utf8CStr) { - unsafe { - __android_log_write(level_to_prio(level), raw_cstr!("Magisk"), msg.as_ptr()); - } + write_android_log(level_to_prio(level), msg); zygisk_log_to_pipe(level_to_prio(level), msg); } @@ -118,6 +111,12 @@ struct LogMeta { tid: i32, } +fn write_android_log(prio: i32, msg: &Utf8CStr) { + unsafe { + __android_log_write(prio, raw_cstr!("Magisk"), msg.as_ptr()); + } +} + const MAX_MSG_LEN: usize = PIPE_BUF - std::mem::size_of::(); fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result { @@ -134,7 +133,14 @@ fn write_log_to_pipe(logd: &mut File, prio: i32, msg: &Utf8CStr) -> io::Result s, }; - let logd_cell = magiskd.logd.lock().unwrap(); - let mut logd_ref = logd_cell.borrow_mut(); - let logd = match logd_ref.as_mut() { + let mut guard = magiskd.logd.lock().unwrap(); + let logd = match guard.as_mut() { None => return, Some(s) => s, }; @@ -154,31 +159,60 @@ fn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { // If any error occurs, shut down the logd pipe if result.is_err() { - *logd_ref = None; + *guard = None; } } -fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { - let magiskd = match MAGISKD.get() { - None => return, - Some(s) => s, - }; +static ZYGISK_LOGD: AtomicI32 = AtomicI32::new(-1); - let logd_cell = magiskd.logd.lock().unwrap(); - let mut logd_ref = logd_cell.borrow_mut(); - if logd_ref.is_none() { +pub fn zygisk_close_logd() { + unsafe { + libc::close(ZYGISK_LOGD.swap(-1, Ordering::Relaxed)); + } +} + +pub fn zygisk_get_logd() -> i32 { + // If we don't have the log pipe set, open the log pipe FIFO. This could actually happen + // multiple times in the zygote daemon (parent process) because we had to close this + // file descriptor to prevent crashing. + // + // For some reason, zygote sanitizes and checks FDs *before* forking. This results in the fact + // that *every* time before zygote forks, it has to close all logging related FDs in order + // to pass FD checks, just to have it re-initialized immediately after any + // logging happens ¯\_(ツ)_/¯. + // + // To be consistent with this behavior, we also have to close the log pipe to magiskd + // to make zygote NOT crash if necessary. We accomplish this by hooking __android_log_close + // and closing it at the same time as the rest of logging FDs. + + let mut fd = ZYGISK_LOGD.load(Ordering::Relaxed); + if fd < 0 { android_logging(); - unsafe { - let fd = zygisk_fetch_logd(); - if fd < 0 { - return; + let mut buf = Utf8CStrBufArr::default(); + let path = FsPathBuf::new(&mut buf) + .join(get_magisk_tmp()) + .join(LOG_PIPE!()); + // Open as RW as sometimes it may block + fd = unsafe { libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC) }; + if fd >= 0 { + // Only re-enable zygisk logging if success + zygisk_logging(); + unsafe { + libc::close(ZYGISK_LOGD.swap(fd, Ordering::Relaxed)); } - *logd_ref = Some(File::from_raw_fd(fd)); + } else { + return -1; } - // Only re-enable zygisk logging if success - zygisk_logging(); - }; - let logd = logd_ref.as_mut().unwrap(); + } + fd +} + +fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { + let fd = zygisk_get_logd(); + if fd < 0 { + // Cannot talk to pipe, abort + return; + } // Block SIGPIPE let mut mask: sigset_t; @@ -190,7 +224,13 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { pthread_sigmask(SIG_BLOCK, &mask, &mut orig_mask); } - let result = write_log_to_pipe(logd, prio, msg); + let result = { + let mut logd = unsafe { File::from_raw_fd(fd) }; + let result = write_log_to_pipe(&mut logd, prio, msg); + // Make sure the file descriptor is not closed after out of scope + std::mem::forget(logd); + result + }; // Consume SIGPIPE if exists, then restore mask unsafe { @@ -201,7 +241,7 @@ fn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) { // If any error occurs, shut down the logd pipe if result.is_err() { - *logd_ref = None; + zygisk_close_logd(); } } @@ -327,35 +367,36 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { impl MagiskD { pub fn start_log_daemon(&self) { - let mut fds: [i32; 2] = [0; 2]; + let mut buf = Utf8CStrBufArr::default(); + let path = FsPathBuf::new(&mut buf) + .join(get_magisk_tmp()) + .join(LOG_PIPE!()); + unsafe { - if pipe2(fds.as_mut_ptr(), O_CLOEXEC) == 0 { - let logd = self.logd.lock().unwrap(); - *logd.borrow_mut() = Some(File::from_raw_fd(fds[1])); - new_daemon_thread(logfile_writer, fds[0] as *mut c_void); - } + libc::mkfifo(path.as_ptr(), 0o666); + libc::chown(path.as_ptr(), 0, 0); + let read = libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC); + let write = libc::open(path.as_ptr(), O_WRONLY | O_CLOEXEC); + *self.logd.lock().unwrap() = Some(File::from_raw_fd(write)); + new_daemon_thread(logfile_writer, read as *mut c_void); } } pub fn get_log_pipe(&self) -> RawFd { - let logd_cell = self.logd.lock().unwrap(); - let logd_ref = logd_cell.borrow(); - let logd = logd_ref.as_ref(); - match logd { - None => -1, - Some(s) => s.as_raw_fd(), - } + self.logd + .lock() + .unwrap() + .as_ref() + .map_or(-1, |s| s.as_raw_fd()) } pub fn close_log_pipe(&self) { - let guard = self.logd.lock().unwrap(); - *guard.borrow_mut() = None; + *self.logd.lock().unwrap() = None; } pub fn setup_logfile(&self) { - let logd_cell = self.logd.lock().unwrap(); - let mut logd_ref = logd_cell.borrow_mut(); - let logd = match logd_ref.as_mut() { + let mut guard = self.logd.lock().unwrap(); + let logd = match guard.as_mut() { None => return, Some(s) => s, }; diff --git a/native/src/core/magisk.cpp b/native/src/core/magisk.cpp index 01e568214e88..3d4f264b49dc 100644 --- a/native/src/core/magisk.cpp +++ b/native/src/core/magisk.cpp @@ -2,13 +2,11 @@ #include #include -#include -#include +#include +#include #include #include -#include "core.hpp" - using namespace std; [[noreturn]] static void usage() { @@ -129,9 +127,9 @@ int magisk_main(int argc, char *argv[]) { write_int(fd, do_reboot); return read_int(fd); } else if (argv[1] == "--path"sv) { - string path = find_magisk_tmp(); - if (!path.empty()) { - printf("%s\n", path.data()); + const char *path = get_magisk_tmp(); + if (path[0] != '\0') { + printf("%s\n", path); return 0; } return 1; diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index a37a82a3d2b0..280516f51dc2 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -1,20 +1,22 @@ #include +#include #include #include #include #include -#include -#include +#include +#include #include -#include "core.hpp" #include "node.hpp" using namespace std; #define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from) +static string native_bridge = "0"; + static int bind_mount(const char *reason, const char *from, const char *to) { int ret = xmount(from, to, nullptr, MS_BIND | MS_REC, nullptr); if (ret == 0) @@ -22,9 +24,6 @@ static int bind_mount(const char *reason, const char *from, const char *to) { return ret; } -string node_entry::module_mnt; -string node_entry::mirror_dir; - /************************* * Node Tree Construction *************************/ @@ -170,7 +169,7 @@ void tmpfs_node::mount() { else src_path = parent()->node_path().data(); if (!isa(parent())) { - auto worker_dir = MAGISKTMP + "/" WORKERDIR + dest; + auto worker_dir = get_magisk_tmp() + "/"s WORKERDIR + dest; mkdirs(worker_dir.data(), 0); create_and_mount(skip_mirror() ? "replace" : "tmpfs", worker_dir); } else { @@ -190,7 +189,7 @@ class magisk_node : public node_entry { explicit magisk_node(const char *name) : node_entry(name, DT_REG, this) {} void mount() override { - const string src = MAGISKTMP + "/" + name(); + const string src = get_magisk_tmp() + "/"s + name(); if (access(src.data(), F_OK)) return; @@ -211,6 +210,21 @@ class magisk_node : public node_entry { } }; +class zygisk_node : public node_entry { +public: + explicit zygisk_node(const char *name, bool is64bit) : node_entry(name, DT_REG, this), + is64bit(is64bit) {} + + void mount() override { + const string src = get_magisk_tmp() + "/magisk"s + (is64bit ? "64" : "32"); + create_and_mount("zygisk", src); + xmount(nullptr, node_path().data(), nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr); + } + +private: + bool is64bit; +}; + static void inject_magisk_bins(root_node *system) { auto bin = system->get_child("bin"); if (!bin) { @@ -228,27 +242,31 @@ static void inject_magisk_bins(root_node *system) { delete bin->extract("supolicy"); } -vector *module_list; -int app_process_32 = -1; -int app_process_64 = -1; - -#define mount_zygisk(bit) \ -if (access("/system/bin/app_process" #bit, F_OK) == 0) { \ - app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC); \ - string zbin = zygisk_bin + "/app_process" #bit; \ - string mbin = MAGISKTMP + "/magisk" #bit; \ - int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC); \ - int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \ - xsendfile(out, src, nullptr, INT_MAX); \ - close(out); \ - close(src); \ - clone_attr("/system/bin/app_process" #bit, zbin.data()); \ - bind_mount("zygisk", zbin.data(), "/system/bin/app_process" #bit); \ +static void inject_zygisk_libs(root_node *system) { + if (access("/system/bin/linker", F_OK) == 0) { + auto lib = system->get_child("lib"); + if (!lib) { + lib = new inter_node("lib"); + system->insert(lib); + } + lib->insert(new zygisk_node(native_bridge.data(), false)); + } + + if (access("/system/bin/linker64", F_OK) == 0) { + auto lib64 = system->get_child("lib64"); + if (!lib64) { + lib64 = new inter_node("lib64"); + system->insert(lib64); + } + lib64->insert(new zygisk_node(native_bridge.data(), true)); + } } +vector *module_list; + void load_modules() { - node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR; - node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/"; + node_entry::mirror_dir = get_magisk_tmp() + "/"s MIRRDIR; + node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/"; auto root = make_unique(""); auto system = new root_node("system"); @@ -258,7 +276,7 @@ void load_modules() { LOGI("* Loading modules\n"); for (const auto &m : *module_list) { const char *module = m.name.data(); - char *b = buf + sprintf(buf, "%s/" MODULEMNT "/%s/", MAGISKTMP.data(), module); + char *b = buf + ssprintf(buf, sizeof(buf), "%s/" MODULEMNT "/%s/", get_magisk_tmp(), module); // Read props strcpy(b, "system.prop"); @@ -284,11 +302,27 @@ void load_modules() { system->collect_module_files(module, fd); close(fd); } - if (MAGISKTMP != "/sbin" || !str_contains(getenv("PATH") ?: "", "/sbin")) { + if (get_magisk_tmp() != "/sbin"sv || !str_contains(getenv("PATH") ?: "", "/sbin")) { // Need to inject our binaries into /system/bin inject_magisk_bins(system); } + if (zygisk_enabled) { + string native_bridge_orig = get_prop(NBPROP); + if (native_bridge_orig.empty()) { + native_bridge_orig = "0"; + } + native_bridge = native_bridge_orig != "0" ? ZYGISKLDR + native_bridge_orig : ZYGISKLDR; + set_prop(NBPROP, native_bridge.data(), true); + // Weather Huawei's Maple compiler is enabled. + // If so, system server will be created by a special Zygote which ignores the native bridge + // and make system server out of our control. Avoid it by disabling. + if (get_prop("ro.maple.enable") == "1") { + set_prop("ro.maple.enable", "0", true); + } + inject_zygisk_libs(system); + } + if (!system->is_empty()) { // Handle special read-only partitions for (const char *part : { "/vendor", "/product", "/system_ext" }) { @@ -304,16 +338,8 @@ void load_modules() { root->mount(); } - // Mount on top of modules to enable zygisk - if (zygisk_enabled) { - string zygisk_bin = MAGISKTMP + "/" ZYGISKBIN; - mkdir(zygisk_bin.data(), 0); - mount_zygisk(32) - mount_zygisk(64) - } - - auto worker_dir = MAGISKTMP + "/" WORKERDIR; - xmount(nullptr, worker_dir.data(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr); + ssprintf(buf, sizeof(buf), "%s/" WORKERDIR, get_magisk_tmp()); + xmount(nullptr, buf, nullptr, MS_REMOUNT | MS_RDONLY, nullptr); } /************************ @@ -448,7 +474,7 @@ void handle_modules() { } static int check_rules_dir(char *buf, size_t sz) { - int off = ssprintf(buf, sz, "%s/%s", MAGISKTMP.data(), PREINITMIRR); + int off = ssprintf(buf, sz, "%s/" PREINITMIRR, get_magisk_tmp()); struct stat st1{}; struct stat st2{}; if (xstat(buf, &st1) < 0 || xstat(MODULEROOT, &st2) < 0) @@ -491,3 +517,23 @@ void exec_module_scripts(const char *stage) { [](const module_info &info) -> string_view { return info.name; }); exec_module_scripts(stage, module_names); } + +void reset_zygisk(bool restore) { + if (!zygisk_enabled) return; + static atomic_uint zygote_start_count{1}; + if (restore) { + zygote_start_count = 1; + } else if (zygote_start_count.fetch_add(1) > 3) { + LOGW("zygote crashes too many times, rolling-back\n"); + restore = true; + } + if (restore) { + string native_bridge_orig = "0"; + if (native_bridge.length() > strlen(ZYGISKLDR)) { + native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); + } + set_prop(NBPROP, native_bridge_orig.data(), true); + } else { + set_prop(NBPROP, native_bridge.data(), true); + } +} diff --git a/native/src/core/node.hpp b/native/src/core/node.hpp index aba9a44c2712..a75d290ed20e 100644 --- a/native/src/core/node.hpp +++ b/native/src/core/node.hpp @@ -49,8 +49,8 @@ class node_entry { virtual void mount() = 0; - static string module_mnt; - static string mirror_dir; + inline static string module_mnt; + inline static string mirror_dir; protected: template diff --git a/native/src/core/package.cpp b/native/src/core/package.cpp index 82a45b01b996..e26ba38ca72d 100644 --- a/native/src/core/package.cpp +++ b/native/src/core/package.cpp @@ -1,11 +1,9 @@ #include -#include -#include +#include +#include #include #include -#include "core.hpp" - using namespace std; using rust::Vec; @@ -73,7 +71,7 @@ vector get_app_no_list() { void preserve_stub_apk() { mutex_guard g(pkg_lock); - string stub_path = MAGISKTMP + "/stub.apk"; + string stub_path = get_magisk_tmp() + "/stub.apk"s; stub_apk_fd = xopen(stub_path.data(), O_RDONLY | O_CLOEXEC); unlink(stub_path.data()); auto cert = read_certificate(stub_apk_fd, -1); diff --git a/native/src/core/resetprop/resetprop.cpp b/native/src/core/resetprop/resetprop.cpp index f5606e7093ae..a9eca71f7827 100644 --- a/native/src/core/resetprop/resetprop.cpp +++ b/native/src/core/resetprop/resetprop.cpp @@ -4,9 +4,11 @@ #include #include +#include +#include -#include "resetprop.hpp" -#include "../core-rs.hpp" +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include using namespace std; diff --git a/native/src/core/scripting.cpp b/native/src/core/scripting.cpp index 28080be3af5d..e2a5d5a2ad61 100644 --- a/native/src/core/scripting.cpp +++ b/native/src/core/scripting.cpp @@ -2,12 +2,10 @@ #include #include -#include +#include #include #include -#include - -#include "core.hpp" +#include using namespace std; @@ -15,15 +13,17 @@ using namespace std; static const char *bbpath() { static string path; - if (path.empty()) - path = MAGISKTMP + "/" BBPATH "/busybox"; + if (path.empty()) { + path = get_magisk_tmp(); + path += "/" BBPATH "/busybox"; + } return path.data(); } static void set_script_env() { setenv("ASH_STANDALONE", "1", 1); char new_path[4096]; - sprintf(new_path, "%s:%s", getenv("PATH"), MAGISKTMP.data()); + ssprintf(new_path, sizeof(new_path), "%s:%s", getenv("PATH"), get_magisk_tmp()); setenv("PATH", new_path, 1); if (zygisk_enabled) setenv("ZYGISK_ENABLED", "1", 1); diff --git a/native/src/core/selinux.cpp b/native/src/core/selinux.cpp index db49407c0427..5dfb240a5766 100644 --- a/native/src/core/selinux.cpp +++ b/native/src/core/selinux.cpp @@ -2,9 +2,10 @@ #include #include -#include +#include #include #include +#include #include using namespace std; @@ -121,14 +122,18 @@ void restorecon() { } void restore_tmpcon() { - if (MAGISKTMP == "/sbin") - setfilecon(MAGISKTMP.data(), ROOT_CON); + const char *tmp = get_magisk_tmp(); + if (tmp == "/sbin"sv) + setfilecon(tmp, ROOT_CON); else - chmod(MAGISKTMP.data(), 0711); + chmod(tmp, 0711); - auto dir = xopen_dir(MAGISKTMP.data()); + auto dir = xopen_dir(tmp); int dfd = dirfd(dir.get()); for (dirent *entry; (entry = xreaddir(dir.get()));) setfilecon_at(dfd, entry->d_name, SYSTEM_CON); + + string logd = tmp + "/"s LOG_PIPE; + setfilecon(logd.data(), MAGISK_LOG_CON); } diff --git a/native/src/core/socket.cpp b/native/src/core/socket.cpp index 57f70553b860..a499b10a7973 100644 --- a/native/src/core/socket.cpp +++ b/native/src/core/socket.cpp @@ -73,13 +73,27 @@ static void *recv_fds(int sockfd, char *cmsgbuf, size_t bufsz, int cnt) { }; xrecvmsg(sockfd, &msg, MSG_WAITALL); - cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (msg.msg_controllen != bufsz) { + LOGE("recv_fd: msg_flags = %d, msg_controllen(%zu) != %zu\n", + msg.msg_flags, msg.msg_controllen, bufsz); + return nullptr; + } - if (msg.msg_controllen != bufsz || - cmsg == nullptr || - cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || - cmsg->cmsg_level != SOL_SOCKET || - cmsg->cmsg_type != SCM_RIGHTS) { + cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == nullptr) { + LOGE("recv_fd: cmsg == nullptr\n"); + return nullptr; + } + if (cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt)) { + LOGE("recv_fd: cmsg_len(%zu) != %zu\n", cmsg->cmsg_len, CMSG_LEN(sizeof(int) * cnt)); + return nullptr; + } + if (cmsg->cmsg_level != SOL_SOCKET) { + LOGE("recv_fd: cmsg_level != SOL_SOCKET\n"); + return nullptr; + } + if (cmsg->cmsg_type != SCM_RIGHTS) { + LOGE("recv_fd: cmsg_type != SCM_RIGHTS\n"); return nullptr; } @@ -92,8 +106,11 @@ vector recv_fds(int sockfd) { // Peek fd count to allocate proper buffer int cnt; recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK); - if (cnt == 0) + if (cnt == 0) { + // Consume data + recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL); return results; + } vector cmsgbuf; cmsgbuf.resize(CMSG_SPACE(sizeof(int) * cnt)); @@ -109,6 +126,15 @@ vector recv_fds(int sockfd) { } int recv_fd(int sockfd) { + // Peek fd count + int cnt; + recv(sockfd, &cnt, sizeof(cnt), MSG_PEEK); + if (cnt == 0) { + // Consume data + recv(sockfd, &cnt, sizeof(cnt), MSG_WAITALL); + return -1; + } + char cmsgbuf[CMSG_SPACE(sizeof(int))]; void *data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); diff --git a/native/src/core/su/connect.cpp b/native/src/core/su/connect.cpp index 8020334d6d0c..af78a5e06a4f 100644 --- a/native/src/core/su/connect.cpp +++ b/native/src/core/su/connect.cpp @@ -3,18 +3,18 @@ #include #include -#include +#include #include "su.hpp" using namespace std; #define CALL_PROVIDER \ -exe, "/system/bin", "com.android.commands.content.Content", \ +"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \ "call", "--uri", target, "--user", user, "--method", action #define START_ACTIVITY \ -exe, "/system/bin", "com.android.commands.am.Am", \ +"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ "start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \ "-f", "0x58800020", "--es", "action", action @@ -130,21 +130,10 @@ static bool check_no_error(int fd) { static void exec_cmd(const char *action, vector &data, const shared_ptr &info, bool provider = true) { - char exe[128]; char target[128]; char user[4]; ssprintf(user, sizeof(user), "%d", to_user_id(info->eval_uid)); - if (zygisk_enabled) { -#if defined(__LP64__) - ssprintf(exe, sizeof(exe), "/proc/self/fd/%d", app_process_64); -#else - ssprintf(exe, sizeof(exe), "/proc/self/fd/%d", app_process_32); -#endif - } else { - strscpy(exe, "/system/bin/app_process", sizeof(exe)); - } - // First try content provider call method if (provider) { ssprintf(target, sizeof(target), "content://%s.provider", info->mgr_pkg.data()); @@ -215,7 +204,7 @@ void app_notify(const su_context &ctx) { int app_request(const su_context &ctx) { // Create FIFO char fifo[64]; - ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", MAGISKTMP.data(), ctx.pid); + ssprintf(fifo, sizeof(fifo), "%s/" INTLROOT "/su_request_%d", get_magisk_tmp(), ctx.pid); mkfifo(fifo, 0600); chown(fifo, ctx.info->mgr_uid, ctx.info->mgr_uid); setfilecon(fifo, MAGISK_FILE_CON); diff --git a/native/src/core/su/su.cpp b/native/src/core/su/su.cpp index e84a6713f1b4..ba17839545e1 100644 --- a/native/src/core/su/su.cpp +++ b/native/src/core/su/su.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include diff --git a/native/src/core/su/su.hpp b/native/src/core/su/su.hpp index 2c9b14719bb9..68485c28679f 100644 --- a/native/src/core/su/su.hpp +++ b/native/src/core/su/su.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #define DEFAULT_SHELL "/system/bin/sh" diff --git a/native/src/core/su/su_daemon.cpp b/native/src/core/su/su_daemon.cpp index 8f40d952774c..6334e75d6d90 100644 --- a/native/src/core/su/su_daemon.cpp +++ b/native/src/core/su/su_daemon.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -342,7 +342,7 @@ void su_daemon_handler(int client, const sock_cred *cred) { if (read_int(client)) { string pts; string ptmx; - auto magiskpts = MAGISKTMP + "/" SHELLPTS; + auto magiskpts = get_magisk_tmp() + "/"s SHELLPTS; if (access(magiskpts.data(), F_OK)) { pts = "/dev/pts"; ptmx = "/dev/ptmx"; diff --git a/native/src/core/thread.cpp b/native/src/core/thread.cpp index 4a7a7da633f8..d9a2e0c37740 100644 --- a/native/src/core/thread.cpp +++ b/native/src/core/thread.cpp @@ -2,7 +2,7 @@ #include -#include +#include using namespace std; diff --git a/native/src/zygisk/api.hpp b/native/src/core/zygisk/api.hpp similarity index 100% rename from native/src/zygisk/api.hpp rename to native/src/core/zygisk/api.hpp diff --git a/native/src/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp similarity index 51% rename from native/src/zygisk/entry.cpp rename to native/src/core/zygisk/entry.cpp index becbf1166a41..7344aacca286 100644 --- a/native/src/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -6,91 +6,45 @@ #include #include -#include -#include -#include +#include #include "zygisk.hpp" #include "module.hpp" -#include "deny/deny.hpp" using namespace std; void *self_handle = nullptr; -// Make sure /proc/self/environ is sanitized -// Filter env and reset MM_ENV_END -static void sanitize_environ() { - char *cur = environ[0]; - - for (int i = 0; environ[i]; ++i) { - // Copy all env onto the original stack - size_t len = strlen(environ[i]); - memmove(cur, environ[i], len + 1); - environ[i] = cur; - cur += len + 1; - } - - prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0); -} - -extern "C" void unload_first_stage() { - ZLOGD("unloading first stage\n"); - unmap_all(HIJACK_BIN); - xumount2(HIJACK_BIN, MNT_DETACH); -} - -extern "C" void zygisk_inject_entry(void *handle) { - rust::zygisk_entry(); - ZLOGD("load success\n"); - - char *ld = getenv("LD_PRELOAD"); - if (char *c = strrchr(ld, ':')) { - *c = '\0'; - setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD - } else { - unsetenv("LD_PRELOAD"); - } - - MAGISKTMP = getenv(MAGISKTMP_ENV); +extern "C" [[maybe_unused]] void zygisk_inject_entry(void *handle) { self_handle = handle; - - unsetenv(MAGISKTMP_ENV); - sanitize_environ(); + zygisk_logging(); hook_functions(); + ZLOGD("load success\n"); } -// The following code runs in zygote/app process - -extern "C" int zygisk_fetch_logd() { - // If we don't have the log pipe set, request magiskd for it. This could actually happen - // multiple times in the zygote daemon (parent process) because we had to close this - // file descriptor to prevent crashing. - // - // For some reason, zygote sanitizes and checks FDs *before* forking. This results in the fact - // that *every* time before zygote forks, it has to close all logging related FDs in order - // to pass FD checks, just to have it re-initialized immediately after any - // logging happens ¯\_(ツ)_/¯. - // - // To be consistent with this behavior, we also have to close the log pipe to magiskd - // to make zygote NOT crash if necessary. For nativeForkAndSpecialize, we can actually - // add this FD into fds_to_ignore to pass the check. For other cases, we accomplish this by - // hooking __android_log_close and closing it at the same time as the rest of logging FDs. - - if (int fd = zygisk_request(ZygiskRequest::GET_LOG_PIPE); fd >= 0) { - int log_pipe = -1; - if (read_int(fd) == 0) { - log_pipe = recv_fd(fd); - } - close(fd); - if (log_pipe >= 0) { - return log_pipe; +static bool is_compatible_with(uint32_t) { + auto name = get_prop(NBPROP); + android_dlextinfo info = { + .flags = ANDROID_DLEXT_FORCE_LOAD + }; + void *handle = android_dlopen_ext(name.data(), RTLD_LAZY, &info); + if (handle) { + auto entry = reinterpret_cast(dlsym(handle, "zygisk_inject_entry")); + if (entry) { + entry(handle); } } - - return -1; + return false; } +extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf{ + .version = 2, + .padding = {}, + .isCompatibleWith = &is_compatible_with, +}; + +// The following code runs in zygote/app process + static inline bool should_load_modules(uint32_t flags) { return (flags & UNMOUNT_MASK) != UNMOUNT_MASK && (flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP; @@ -157,12 +111,13 @@ static void connect_companion(int client, bool is_64_bit) { socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds); zygiskd_socket = fds[0]; if (fork_dont_care() == 0) { - string exe = MAGISKTMP + "/magisk" + (is_64_bit ? "64" : "32"); + char exe[64]; + ssprintf(exe, sizeof(exe), "%s/magisk%s", get_magisk_tmp(), (is_64_bit ? "64" : "32")); // This fd has to survive exec fcntl(fds[1], F_SETFD, 0); char buf[16]; ssprintf(buf, sizeof(buf), "%d", fds[1]); - execl(exe.data(), "", "zygisk", "companion", buf, (char *) nullptr); + execl(exe, "", "zygisk", "companion", buf, (char *) nullptr); exit(-1); } close(fds[1]); @@ -177,89 +132,6 @@ static void connect_companion(int client, bool is_64_bit) { send_fd(zygiskd_socket, client); } -static timespec last_zygote_start; -static int zygote_start_counts[] = { 0, 0 }; -#define zygote_start_count zygote_start_counts[is_64_bit] -#define zygote_started (zygote_start_counts[0] + zygote_start_counts[1]) -#define zygote_start_reset(val) { zygote_start_counts[0] = val; zygote_start_counts[1] = val; } - -static void setup_files(int client, const sock_cred *cred) { - LOGD("zygisk: setup files for pid=[%d]\n", cred->pid); - - char buf[4096]; - if (!get_exe(cred->pid, buf, sizeof(buf))) { - write_int(client, 1); - return; - } - - // Hijack some binary in /system/bin to host loader - const char *hbin; - string mbin; - int app_fd; - bool is_64_bit = str_ends(buf, "64"); - if (is_64_bit) { - hbin = HIJACK_BIN64; - mbin = MAGISKTMP + "/" ZYGISKBIN "/loader64.so"; - app_fd = app_process_64; - } else { - hbin = HIJACK_BIN32; - mbin = MAGISKTMP + "/" ZYGISKBIN "/loader32.so"; - app_fd = app_process_32; - } - - if (!zygote_started) { - // First zygote launch, record time - clock_gettime(CLOCK_MONOTONIC, &last_zygote_start); - } - - if (zygote_start_count) { - // This zygote ABI had started before, kill existing zygiskd - close(zygiskd_sockets[0]); - close(zygiskd_sockets[1]); - zygiskd_sockets[0] = -1; - zygiskd_sockets[1] = -1; - xumount2(hbin, MNT_DETACH); - } - ++zygote_start_count; - - if (zygote_start_count >= 5) { - // Bootloop prevention - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - if (ts.tv_sec - last_zygote_start.tv_sec > 60) { - // This is very likely manual soft reboot - memcpy(&last_zygote_start, &ts, sizeof(ts)); - zygote_start_reset(1); - } else { - // If any zygote relaunched more than 5 times within a minute, - // don't do any setups further to prevent bootloop. - zygote_start_reset(999); - write_int(client, 1); - return; - } - } - - // Ack - write_int(client, 0); - - // Receive and bind mount loader - int ld_fd = xopen(mbin.data(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0755); - string ld_data = read_string(client); - xwrite(ld_fd, ld_data.data(), ld_data.size()); - close(ld_fd); - setfilecon(mbin.data(), MAGISK_FILE_CON); - xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr); - - send_fd(client, app_fd); - write_string(client, MAGISKTMP); -} - -static void magiskd_passthrough(int client) { - bool is_64_bit = read_int(client); - write_int(client, 0); - send_fd(client, is_64_bit ? app_process_64 : app_process_32); -} - extern bool uid_granted_root(int uid); static void get_process_info(int client, const sock_cred *cred) { int uid = read_int(client); @@ -320,16 +192,6 @@ static void get_process_info(int client, const sock_cred *cred) { } } -static void send_log_pipe(int fd) { - int logd_fd = rust::get_magiskd().get_log_pipe(); - if (logd_fd >= 0) { - write_int(fd, 0); - send_fd(fd, logd_fd); - } else { - write_int(fd, 1); - } -} - static void get_moddir(int client) { int id = read_int(client); char buf[4096]; @@ -343,18 +205,9 @@ void zygisk_handler(int client, const sock_cred *cred) { int code = read_int(client); char buf[256]; switch (code) { - case ZygiskRequest::SETUP: - setup_files(client, cred); - break; - case ZygiskRequest::PASSTHROUGH: - magiskd_passthrough(client); - break; case ZygiskRequest::GET_INFO: get_process_info(client, cred); break; - case ZygiskRequest::GET_LOG_PIPE: - send_log_pipe(client); - break; case ZygiskRequest::CONNECT_COMPANION: if (get_exe(cred->pid, buf, sizeof(buf))) { connect_companion(client, str_ends(buf, "64")); diff --git a/native/src/zygisk/gen_jni_hooks.py b/native/src/core/zygisk/gen_jni_hooks.py similarity index 82% rename from native/src/zygisk/gen_jni_hooks.py rename to native/src/core/zygisk/gen_jni_hooks.py index 7a759b4d5bcb..11b9035665c7 100755 --- a/native/src/zygisk/gen_jni_hooks.py +++ b/native/src/core/zygisk/gen_jni_hooks.py @@ -95,7 +95,7 @@ def body(self): for a in self.args: if a.set_arg: decl += ind(1) + f'args.{a.name} = &{a.name};' - decl += ind(1) + 'HookContext ctx(env, &args);' + decl += ind(1) + 'ZygiskContext ctx(env, &args);' decl += ind(1) + f'ctx.{self.base_name()}_pre();' decl += ind(1) + self.orig_method() + '(' decl += ind(2) + f'env, clazz, {self.name_list()}' @@ -213,60 +213,21 @@ def gen_jni_def(clz, methods): decl += ind(1) + f'return {m.ret.value};' decl += ind(0) + '}' - decl += ind(0) + f'const JNINativeMethod {m.base_name()}_methods[] = {{' + decl += ind(0) + f'std::array {m.base_name()}_methods = {{' for m in methods: - decl += ind(1) + '{' + decl += ind(1) + 'JNINativeMethod {' decl += ind(2) + f'"{m.base_name()}",' decl += ind(2) + f'"{m.jni()}",' decl += ind(2) + f'(void *) &{m.name}' decl += ind(1) + '},' decl += ind(0) + '};' decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl - decl += ind(0) + f'constexpr int {m.base_name()}_methods_num = std::size({m.base_name()}_methods);' decl += ind(0) hook_map[clz].append(m.base_name()) return decl -def gen_jni_hook(): - decl = '' - decl += ind(0) + 'static JNINativeMethod *hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {' - decl += ind(1) + 'JNINativeMethod *newMethods = nullptr;' - decl += ind(1) + 'int clz_id = -1;' - decl += ind(1) + 'int hook_cnt = 0;' - decl += ind(1) + 'do {' - - for index, (clz, methods) in enumerate(hook_map.items()): - decl += ind(2) + f'if (className == "{clz}"sv) {{' - decl += ind(3) + f'clz_id = {index};' - decl += ind(3) + f'hook_cnt = {len(methods)};' - decl += ind(3) + 'break;' - decl += ind(2) + '}' - - decl += ind(1) + '} while (false);' - - decl += ind(1) + 'if (hook_cnt) {' - decl += ind(2) + 'newMethods = new JNINativeMethod[numMethods];' - decl += ind(2) + 'memcpy(newMethods, methods, sizeof(JNINativeMethod) * numMethods);' - decl += ind(1) + '}' - - decl += ind(1) + 'auto &class_map = (*jni_method_map)[className];' - decl += ind(1) + 'for (int i = 0; i < numMethods; ++i) {' - - for index, methods in enumerate(hook_map.values()): - decl += ind(2) + f'if (hook_cnt && clz_id == {index}) {{' - for m in methods: - decl += ind(3) + f'HOOK_JNI({m})' - decl += ind(2) + '}' - - decl += ind(2) + 'class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;' - decl += ind(1) + '}' - - decl += ind(1) + 'return newMethods;' - decl += ind(0) + '}' - return decl - with open('jni_hooks.hpp', 'w') as f: f.write('// Generated by gen_jni_hooks.py\n') f.write('\nnamespace {\n') @@ -283,6 +244,3 @@ def gen_jni_hook(): f.write(gen_jni_def(zygote, methods)) f.write('\n} // namespace\n') - - f.write(gen_jni_hook()) - f.write('\n') diff --git a/native/src/core/zygisk/hook.cpp b/native/src/core/zygisk/hook.cpp new file mode 100644 index 000000000000..d9d6d5721305 --- /dev/null +++ b/native/src/core/zygisk/hook.cpp @@ -0,0 +1,595 @@ +#include +#include +#include + +#include + +#include +#include + +#include "zygisk.hpp" +#include "module.hpp" +#include "jni_hooks.hpp" + +using namespace std; + +// ********************* +// Zygisk Bootstrapping +// ********************* +// +// Zygisk's lifecycle is driven by several PLT function hooks in libandroid_runtime, libart, and +// libnative_bridge. As Zygote is starting up, these carefully selected functions will call into +// the respective lifecycle callbacks in Zygisk to drive the progress forward. +// +// The entire bootstrap process is shown in the graph below. +// Arrows represent control flow, and the blocks are sorted chronologically from top to bottom. +// +// libnative_bridge libandroid_runtime zygisk libart +// +// ┌───────┐ +// │ start │ +// └───┬─┬─┘ +// │ │ ┌────────────────┐ +// │ └────────────────────────────────────────►│LoadNativeBridge│ +// │ └───────┬────────┘ +// ┌────────────────┐ │ │ +// │LoadNativeBridge│◄────────────┼───────────────────────────────────────────────────┘ +// └───────┬────┬───┘ │ +// │ │ │ ┌───────────────┐ +// │ └─────────────────┼────────────────────►│NativeBridgeItf│ +// │ │ └──────┬────────┘ +// │ │ │ +// │ │ ▼ +// │ │ ┌────────┐ +// │ │ │hook_plt│ +// ▼ │ └────────┘ +// ┌───────┐ │ +// │dlclose│ │ +// └───┬───┘ │ +// │ │ +// │ │ ┌───────────────────────┐ +// └──────────────────────┼────────────────►│post_native_bridge_load│ +// │ └───────────────────────┘ +// ▼ +// ┌──────────────────────────┐ +// │androidSetCreateThreadFunc│ +// └─────────────┬────┬───────┘ +// │ │ ┌────────────┐ +// │ └────────────────►│hook_jni_env│ +// ▼ └────────────┘ +// ┌──────────────────┐ +// │register_jni_procs│ +// └────────┬────┬────┘ +// │ │ ┌───────────────────┐ +// │ └─────────────►│replace_jni_methods│ +// │ └───────────────────┘ ┌─────────┐ +// │ │ │ +// └────────────────────────────────────────────►│ JVM │ +// │ │ +// └──┬─┬────┘ +// ┌───────────────────┐ │ │ +// │nativeXXXSpecialize│◄─────────────────────────────────────┘ │ +// └─────────────┬─────┘ │ +// │ ┌─────────────┐ │ +// └────────────────►│ZygiskContext│ │ +// └─────────────┘ ▼ +// ┌────────────────────┐ +// │pthread_attr_destroy│ +// └─────────┬──────────┘ +// ┌────────────────┐ │ +// │restore_plt_hook│◄───────────┘ +// └────────────────┘ +// +// Some notes regarding the important functions/symbols during bootstrap: +// +// * NativeBridgeItf: this symbol is the entry point for android::LoadNativeBridge +// * HookContext::hook_plt(): hook functions like |dlclose| and |androidSetCreateThreadFunc| +// * dlclose: the final step before android::LoadNativeBridge returns +// * androidSetCreateThreadFunc: called in AndroidRuntime::startReg before +// |register_jni_procs|, which is when most native JNI methods are registered. +// * HookContext::hook_jni_env(): replace the |RegisterNatives| function pointer in JNIEnv. +// * replace_jni_methods: called in the replaced |RegisterNatives| function to filter and replace +// the function pointers registered in register_jni_procs, most importantly the process +// specialization routines, which are our main targets. This marks the final step +// of the code injection bootstrap process. +// * pthread_attr_destroy: called whenever the JVM tries to setup threads for itself. We use +// this method to cleanup and unload Zygisk from the process. + +struct HookContext { + vector> plt_backup; + map, StringCmp> jni_backup; + JNINativeInterface new_env{}; + const JNINativeInterface *old_env = nullptr; + const NativeBridgeRuntimeCallbacks *runtime_callbacks = nullptr; + + void hook_plt(); + void hook_unloader(); + void restore_plt_hook(); + void hook_jni_env(); + void restore_jni_hook(JNIEnv *env); + void post_native_bridge_load(); + +private: + void register_hook(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func); +}; + +// Global contexts: +// +// HookContext lives as long as Zygisk is loaded in memory. It tracks the process's function +// hooking state and bootstraps code injection until we replace the process specialization methods. +// +// ZygiskContext lives during the process specialization process. It implements Zygisk +// features, such as loading modules and customizing process fork/specialization. + +ZygiskContext *g_ctx; +static HookContext *g_hook; +static bool should_unmap_zygisk = false; + +// ----------------------------------------------------------------- + +#define DCL_HOOK_FUNC(ret, func, ...) \ +ret (*old_##func)(__VA_ARGS__); \ +ret new_##func(__VA_ARGS__) + +DCL_HOOK_FUNC(static void, androidSetCreateThreadFunc, void *func) { + ZLOGD("androidSetCreateThreadFunc\n"); + g_hook->hook_jni_env(); + old_androidSetCreateThreadFunc(func); +} + +// Skip actual fork and return cached result if applicable +DCL_HOOK_FUNC(int, fork) { + return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); +} + +// Unmount stuffs in the process's private mount namespace +DCL_HOOK_FUNC(static int, unshare, int flags) { + int res = old_unshare(flags); + if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0 && + // For some unknown reason, unmounting app_process in SysUI can break. + // This is reproducible on the official AVD running API 26 and 27. + // Simply avoid doing any unmounts for SysUI to avoid potential issues. + (g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) { + if (g_ctx->flags & DO_REVERT_UNMOUNT) { + revert_unmount(); + } + // Restore errno back to 0 + errno = 0; + } + return res; +} + +// This is the last moment before the secontext of the process changes +DCL_HOOK_FUNC(static int, selinux_android_setcontext, + uid_t uid, bool isSystemServer, const char *seinfo, const char *pkgname) { + // Pre-fetch logd before secontext transition + zygisk_get_logd(); + return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname); +} + +// Close file descriptors to prevent crashing +DCL_HOOK_FUNC(static void, android_log_close) { + if (g_ctx == nullptr || !(g_ctx->flags & SKIP_CLOSE_LOG_PIPE)) { + // This happens during forks like nativeForkApp, nativeForkUsap, + // nativeForkSystemServer, and nativeForkAndSpecialize. + zygisk_close_logd(); + } + old_android_log_close(); +} + +// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns, +// it will return to our code which has been unmapped, causing segmentation fault. +// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start. +DCL_HOOK_FUNC(static int, pthread_attr_destroy, void *target) { + int res = old_pthread_attr_destroy((pthread_attr_t *)target); + + // Only perform unloading on the main thread + if (gettid() != getpid()) + return res; + + ZLOGV("pthread_attr_destroy\n"); + if (should_unmap_zygisk) { + g_hook->restore_plt_hook(); + if (should_unmap_zygisk) { + delete g_hook; + + // Because both `pthread_attr_destroy` and `dlclose` have the same function signature, + // we can use `musttail` to let the compiler reuse our stack frame and thus + // `dlclose` will directly return to the caller of `pthread_attr_destroy`. + [[clang::musttail]] return dlclose(self_handle); + } + } + + delete g_hook; + return res; +} + +// it should be safe to assume all dlclose's in libnativebridge are for zygisk_loader +DCL_HOOK_FUNC(static int, dlclose, void *handle) { + static bool kDone = false; + if (!kDone) { + ZLOGV("dlclose zygisk_loader\n"); + kDone = true; + g_hook->post_native_bridge_load(); + } + [[clang::musttail]] return old_dlclose(handle); +} + +#undef DCL_HOOK_FUNC + +// ----------------------------------------------------------------- + +ZygiskContext::ZygiskContext(JNIEnv *env, void *args) : + env(env), args{args}, process(nullptr), pid(-1), flags(0), info_flags(0), + hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; } + +ZygiskContext::~ZygiskContext() { + // This global pointer points to a variable on the stack. + // Set this to nullptr to prevent leaking local variable. + // This also disables most plt hooked functions. + g_ctx = nullptr; + + if (!is_child()) + return; + + zygisk_close_logd(); + android_logging(); + + // Strip out all API function pointers + for (auto &m : modules) { + m.clearApi(); + } + + // Cleanup + should_unmap_zygisk = true; + g_hook->restore_jni_hook(env); + g_hook->hook_unloader(); +} + +// ----------------------------------------------------------------- + +inline void *unwind_get_region_start(_Unwind_Context *ctx) { + auto fp = _Unwind_GetRegionStart(ctx); +#if defined(__arm__) + // On arm32, we need to check if the pc is in thumb mode, + // if so, we need to set the lowest bit of fp to 1 + auto pc = _Unwind_GetGR(ctx, 15); // r15 is pc + if (pc & 1) { + // Thumb mode + fp |= 1; + } +#endif + return reinterpret_cast(fp); +} + +// As we use NativeBridgeRuntimeCallbacks to reload native bridge and to hook jni functions, +// we need to find it by the native bridge's unwind context. +// For abis that use registers to pass arguments, i.e. arm32, arm64, x86_64, the registers are +// caller-saved, and they are not preserved in the unwind context. However, they will be saved +// into the callee-saved registers, so we will search the callee-saved registers for the second +// argument, which is the pointer to NativeBridgeRuntimeCallbacks. +// For x86, whose abi uses stack to pass arguments, we can directly get the pointer to +// NativeBridgeRuntimeCallbacks from the stack. +static const NativeBridgeRuntimeCallbacks* find_runtime_callbacks(struct _Unwind_Context *ctx) { + // Find the writable memory region of libart.so, where the NativeBridgeRuntimeCallbacks is located. + auto [start, end] = []()-> tuple { + for (const auto &map : lsplt::MapInfo::Scan()) { + if (map.path.ends_with("/libart.so") && map.perms == (PROT_WRITE | PROT_READ)) { + ZLOGV("libart.so: start=%p, end=%p\n", + reinterpret_cast(map.start), reinterpret_cast(map.end)); + return {map.start, map.end}; + } + } + return {0, 0}; + }(); +#if defined(__aarch64__) + // r19-r28 are callee-saved registers + for (int i = 19; i <= 28; ++i) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#elif defined(__arm__) + // r4-r10 are callee-saved registers + for (int i = 4; i <= 10; ++i) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#elif defined(__i386__) + // get ebp, which points to the bottom of the stack frame + auto ebp = static_cast(_Unwind_GetGR(ctx, 5)); + // 1 pointer size above ebp is the old ebp + // 2 pointer sizes above ebp is the return address + // 3 pointer sizes above ebp is the 2nd arg + auto val = *reinterpret_cast(ebp + 3 * sizeof(void *)); + ZLOGV("ebp + 3 * ptr_size = %p\n", reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); +#elif defined(__x86_64__) + // r12-r15 and rbx are callee-saved registers, but the compiler is likely to use them reversely + for (int i : {3, 15, 14, 13, 12}) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#else +#error "Unsupported architecture" +#endif + return nullptr; +} + +void HookContext::post_native_bridge_load() { + using method_sig = const bool (*)(const char *, const NativeBridgeRuntimeCallbacks *); + struct trace_arg { + method_sig load_native_bridge; + const NativeBridgeRuntimeCallbacks *callbacks; + }; + trace_arg arg{}; + + // Unwind to find the address of android::LoadNativeBridge and NativeBridgeRuntimeCallbacks + _Unwind_Backtrace(+[](_Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code { + void *fp = unwind_get_region_start(ctx); + Dl_info info{}; + dladdr(fp, &info); + ZLOGV("backtrace: %p %s\n", fp, info.dli_fname ?: "???"); + if (info.dli_fname && std::string_view(info.dli_fname).ends_with("/libnativebridge.so")) { + auto payload = reinterpret_cast(arg); + payload->load_native_bridge = reinterpret_cast(fp); + payload->callbacks = find_runtime_callbacks(ctx); + ZLOGV("NativeBridgeRuntimeCallbacks: %p\n", payload->callbacks); + return _URC_END_OF_STACK; + } + return _URC_NO_REASON; + }, &arg); + + if (!arg.load_native_bridge || !arg.callbacks) + return; + + // Reload the real native bridge if necessary + auto nb = get_prop(NBPROP); + auto len = sizeof(ZYGISKLDR) - 1; + if (nb.size() > len) { + arg.load_native_bridge(nb.data() + len, arg.callbacks); + } + runtime_callbacks = arg.callbacks; +} + +// ----------------------------------------------------------------- + +void HookContext::register_hook( + dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) { + if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) { + ZLOGE("Failed to register plt_hook \"%s\"\n", symbol); + return; + } + plt_backup.emplace_back(dev, inode, symbol, old_func); +} + +#define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME) \ + register_hook(DEV, INODE, SYM, \ + reinterpret_cast(new_##NAME), reinterpret_cast(&old_##NAME)) + +#define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ + PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) + +void HookContext::hook_plt() { + ino_t android_runtime_inode = 0; + dev_t android_runtime_dev = 0; + ino_t native_bridge_inode = 0; + dev_t native_bridge_dev = 0; + + for (auto &map : lsplt::MapInfo::Scan()) { + if (map.path.ends_with("/libandroid_runtime.so")) { + android_runtime_inode = map.inode; + android_runtime_dev = map.dev; + } else if (map.path.ends_with("/libnativebridge.so")) { + native_bridge_inode = map.inode; + native_bridge_dev = map.dev; + } + } + + PLT_HOOK_REGISTER(native_bridge_dev, native_bridge_inode, dlclose); + PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); + PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare); + PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc); + PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext); + PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); + + if (!lsplt::CommitHook()) + ZLOGE("plt_hook failed\n"); + + // Remove unhooked methods + plt_backup.erase( + std::remove_if(plt_backup.begin(), plt_backup.end(), + [](auto &t) { return *std::get<3>(t) == nullptr;}), + g_hook->plt_backup.end()); +} + +void HookContext::hook_unloader() { + ino_t art_inode = 0; + dev_t art_dev = 0; + + for (auto &map : lsplt::MapInfo::Scan()) { + if (map.path.ends_with("/libart.so")) { + art_inode = map.inode; + art_dev = map.dev; + break; + } + } + + PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy); + if (!lsplt::CommitHook()) + ZLOGE("plt_hook failed\n"); +} + +void HookContext::restore_plt_hook() { + // Unhook plt_hook + for (const auto &[dev, inode, sym, old_func] : plt_backup) { + if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) { + ZLOGE("Failed to register plt_hook [%s]\n", sym); + should_unmap_zygisk = false; + } + } + if (!lsplt::CommitHook()) { + ZLOGE("Failed to restore plt_hook\n"); + should_unmap_zygisk = false; + } +} + +// ----------------------------------------------------------------- + +static string get_class_name(JNIEnv *env, jclass clazz) { + static auto class_getName = env->GetMethodID( + env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); + auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); + const char *name = env->GetStringUTFChars(nameRef, nullptr); + string className(name); + env->ReleaseStringUTFChars(nameRef, name); + std::replace(className.begin(), className.end(), '.', '/'); + return className; +} + +static void replace_jni_methods( + vector &methods, vector &backup, + const JNINativeMethod *hook_methods, size_t hook_methods_size, + void **orig_function) { + for (auto &method : methods) { + if (strcmp(method.name, hook_methods[0].name) == 0) { + for (auto i = 0; i < hook_methods_size; ++i) { + const auto &hook = hook_methods[i]; + if (strcmp(method.signature, hook.signature) == 0) { + backup.push_back(method); + *orig_function = method.fnPtr; + method.fnPtr = hook.fnPtr; + ZLOGI("replace %s\n", method.name); + return; + } + } + ZLOGE("unknown signature of %s%s\n", method.name, method.signature); + } + } +} + +#define HOOK_JNI(method) \ +replace_jni_methods(newMethods, backup, method##_methods.data(), method##_methods.size(), &method##_orig) + +static jint env_RegisterNatives( + JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { + auto className = get_class_name(env, clazz); + if (className == "com/android/internal/os/Zygote") { + // Restore JNIEnv as we no longer need to replace anything + env->functions = g_hook->old_env; + + vector newMethods(methods, methods + numMethods); + vector &backup = g_hook->jni_backup[className]; + HOOK_JNI(nativeForkAndSpecialize); + HOOK_JNI(nativeSpecializeAppProcess); + HOOK_JNI(nativeForkSystemServer); + return g_hook->old_env->RegisterNatives(env, clazz, newMethods.data(), numMethods); + } else { + return g_hook->old_env->RegisterNatives(env, clazz, methods, numMethods); + } +} + +void HookContext::hook_jni_env() { + using method_sig = jint(*)(JavaVM **, jsize, jsize *); + auto get_created_vms = reinterpret_cast( + dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); + if (!get_created_vms) { + for (auto &map: lsplt::MapInfo::Scan()) { + if (!map.path.ends_with("/libnativehelper.so")) continue; + void *h = dlopen(map.path.data(), RTLD_LAZY); + if (!h) { + ZLOGW("Cannot dlopen libnativehelper.so: %s\n", dlerror()); + break; + } + get_created_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); + dlclose(h); + break; + } + if (!get_created_vms) { + ZLOGW("JNI_GetCreatedJavaVMs not found\n"); + return; + } + } + + JavaVM *vm = nullptr; + jsize num = 0; + jint res = get_created_vms(&vm, 1, &num); + if (res != JNI_OK || vm == nullptr) { + ZLOGW("JavaVM not found\n"); + return; + } + JNIEnv *env = nullptr; + res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (res != JNI_OK || env == nullptr) { + ZLOGW("JNIEnv not found\n"); + return; + } + + // Replace the function table in JNIEnv to hook RegisterNatives + memcpy(&new_env, env->functions, sizeof(*env->functions)); + new_env.RegisterNatives = &env_RegisterNatives; + old_env = env->functions; + env->functions = &new_env; +} + +void HookContext::restore_jni_hook(JNIEnv *env) { + for (const auto &[clz, methods] : jni_backup) { + if (!methods.empty() && env->RegisterNatives( + env->FindClass(clz.data()), methods.data(), + static_cast(methods.size())) != 0) { + ZLOGE("Failed to restore JNI hook of class [%s]\n", clz.data()); + should_unmap_zygisk = false; + } + } +} + +// ----------------------------------------------------------------- + +void hook_functions() { + default_new(g_hook); + g_hook->hook_plt(); +} + +void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { + jclass clazz; + if (!g_hook || !g_hook->runtime_callbacks || !env || !clz || !(clazz = env->FindClass(clz))) { + for (auto i = 0; i < numMethods; ++i) { + methods[i].fnPtr = nullptr; + } + return; + } + + // Backup existing methods + auto total = g_hook->runtime_callbacks->getNativeMethodCount(env, clazz); + vector old_methods(total); + g_hook->runtime_callbacks->getNativeMethods(env, clazz, old_methods.data(), total); + + // Replace the method + for (auto i = 0; i < numMethods; ++i) { + auto &method = methods[i]; + auto res = env->RegisterNatives(clazz, &method, 1); + // It's normal that the method is not found + if (res == JNI_ERR || env->ExceptionCheck()) { + auto exception = env->ExceptionOccurred(); + if (exception) env->DeleteLocalRef(exception); + env->ExceptionClear(); + method.fnPtr = nullptr; + } else { + // Find the old function pointer and return to caller + for (const auto &old_method : old_methods) { + if (strcmp(method.name, old_method.name) == 0 && + strcmp(method.signature, old_method.signature) == 0) { + ZLOGD("replace %s#%s%s %p -> %p\n", clz, + method.name, method.signature, old_method.fnPtr, method.fnPtr); + method.fnPtr = old_method.fnPtr; + } + } + } + } +} diff --git a/native/src/zygisk/jni_hooks.hpp b/native/src/core/zygisk/jni_hooks.hpp similarity index 89% rename from native/src/zygisk/jni_hooks.hpp rename to native/src/core/zygisk/jni_hooks.hpp index 9b62083986bd..1f96395372e1 100644 --- a/native/src/zygisk/jni_hooks.hpp +++ b/native/src/core/zygisk/jni_hooks.hpp @@ -5,7 +5,7 @@ namespace { void *nativeForkAndSpecialize_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir @@ -16,7 +16,7 @@ void *nativeForkAndSpecialize_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.fds_to_ignore = &fds_to_ignore; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir @@ -28,7 +28,7 @@ void *nativeForkAndSpecialize_orig = nullptr; AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir @@ -41,7 +41,7 @@ void *nativeForkAndSpecialize_orig = nullptr; args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app @@ -58,7 +58,7 @@ void *nativeForkAndSpecialize_orig = nullptr; args.whitelisted_data_info_list = &whitelisted_data_info_list; args.mount_data_dirs = &mount_data_dirs; args.mount_storage_dirs = &mount_storage_dirs; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs @@ -68,7 +68,7 @@ void *nativeForkAndSpecialize_orig = nullptr; } [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir @@ -78,7 +78,7 @@ void *nativeForkAndSpecialize_orig = nullptr; } [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4 @@ -89,7 +89,7 @@ void *nativeForkAndSpecialize_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.fds_to_ignore = &fds_to_ignore; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir @@ -101,7 +101,7 @@ void *nativeForkAndSpecialize_orig = nullptr; AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.fds_to_ignore = &fds_to_ignore; args.is_child_zygote = &is_child_zygote; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkAndSpecialize_pre(); reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir @@ -109,60 +109,59 @@ void *nativeForkAndSpecialize_orig = nullptr; ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -const JNINativeMethod nativeForkAndSpecialize_methods[] = { - { +std::array nativeForkAndSpecialize_methods = { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_l }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_p }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I", (void *) &nativeForkAndSpecialize_q_alt }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void *) &nativeForkAndSpecialize_r }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_m }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I", (void *) &nativeForkAndSpecialize_samsung_n }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_p }, }; -constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods); void *nativeSpecializeAppProcess_orig = nullptr; [[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir @@ -173,7 +172,7 @@ void *nativeSpecializeAppProcess_orig = nullptr; AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; args.is_top_app = &is_top_app; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app @@ -188,7 +187,7 @@ void *nativeSpecializeAppProcess_orig = nullptr; args.whitelisted_data_info_list = &whitelisted_data_info_list; args.mount_data_dirs = &mount_data_dirs; args.mount_storage_dirs = &mount_storage_dirs; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs @@ -198,41 +197,40 @@ void *nativeSpecializeAppProcess_orig = nullptr; [[clang::no_stack_protector]] void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeSpecializeAppProcess_pre(); reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeSpecializeAppProcess_post(); } -const JNINativeMethod nativeSpecializeAppProcess_methods[] = { - { +std::array nativeSpecializeAppProcess_methods = { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_q }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V", (void *) &nativeSpecializeAppProcess_q_alt }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", (void *) &nativeSpecializeAppProcess_r }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_samsung_q }, }; -constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods); void *nativeForkSystemServer_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkSystemServer_pre(); reinterpret_cast(nativeForkSystemServer_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities @@ -242,7 +240,7 @@ void *nativeForkSystemServer_orig = nullptr; } [[clang::no_stack_protector]] jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); - HookContext ctx(env, &args); + ZygiskContext ctx(env, &args); ctx.nativeForkSystemServer_pre(); reinterpret_cast(nativeForkSystemServer_orig)( env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities @@ -250,45 +248,17 @@ void *nativeForkSystemServer_orig = nullptr; ctx.nativeForkSystemServer_post(); return ctx.pid; } -const JNINativeMethod nativeForkSystemServer_methods[] = { - { +std::array nativeForkSystemServer_methods = { + JNINativeMethod { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) &nativeForkSystemServer_l }, - { + JNINativeMethod { "nativeForkSystemServer", "(II[IIII[[IJJ)I", (void *) &nativeForkSystemServer_samsung_q }, }; -constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods); } // namespace - -static JNINativeMethod *hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { - JNINativeMethod *newMethods = nullptr; - int clz_id = -1; - int hook_cnt = 0; - do { - if (className == "com/android/internal/os/Zygote"sv) { - clz_id = 0; - hook_cnt = 3; - break; - } - } while (false); - if (hook_cnt) { - newMethods = new JNINativeMethod[numMethods]; - memcpy(newMethods, methods, sizeof(JNINativeMethod) * numMethods); - } - auto &class_map = (*jni_method_map)[className]; - for (int i = 0; i < numMethods; ++i) { - if (hook_cnt && clz_id == 0) { - HOOK_JNI(nativeForkAndSpecialize) - HOOK_JNI(nativeSpecializeAppProcess) - HOOK_JNI(nativeForkSystemServer) - } - class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; - } - return newMethods; -} diff --git a/native/src/core/zygisk/main.cpp b/native/src/core/zygisk/main.cpp new file mode 100644 index 000000000000..85e70d768ab3 --- /dev/null +++ b/native/src/core/zygisk/main.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "zygisk.hpp" + +using namespace std; + +static void zygiskd(int socket) { + if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) + exit(-1); + +#if defined(__LP64__) + set_nice_name("zygiskd64"); + LOGI("* Launching zygiskd64\n"); +#else + set_nice_name("zygiskd32"); + LOGI("* Launching zygiskd32\n"); +#endif + + // Load modules + using comp_entry = void(*)(int); + vector modules; + { + vector module_fds = recv_fds(socket); + for (int fd : module_fds) { + comp_entry entry = nullptr; + struct stat s{}; + if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) { + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fd, + }; + if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { + *(void **) &entry = dlsym(h, "zygisk_companion_entry"); + } else { + LOGW("Failed to dlopen zygisk module: %s\n", dlerror()); + } + } + modules.push_back(entry); + close(fd); + } + } + + // ack + write_int(socket, 0); + + // Start accepting requests + pollfd pfd = { socket, POLLIN, 0 }; + for (;;) { + poll(&pfd, 1, -1); + if (pfd.revents && !(pfd.revents & POLLIN)) { + // Something bad happened in magiskd, terminate zygiskd + exit(0); + } + int client = recv_fd(socket); + if (client < 0) { + // Something bad happened in magiskd, terminate zygiskd + exit(0); + } + int module_id = read_int(client); + if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { + exec_task([=, entry = modules[module_id]] { + struct stat s1; + fstat(client, &s1); + entry(client); + // Only close client if it is the same file so we don't + // accidentally close a re-used file descriptor. + // This check is required because the module companion + // handler could've closed the file descriptor already. + if (struct stat s2; fstat(client, &s2) == 0) { + if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { + close(client); + } + } + }); + } else { + close(client); + } + } +} + +// Entrypoint where we need to re-exec ourselves +// This should only ever be called internally +int zygisk_main(int argc, char *argv[]) { + android_logging(); + if (argc == 3 && argv[1] == "companion"sv) { + zygiskd(parse_int(argv[2])); + } + return 0; +} diff --git a/native/src/core/zygisk/module.cpp b/native/src/core/zygisk/module.cpp new file mode 100644 index 000000000000..38065178f9b4 --- /dev/null +++ b/native/src/core/zygisk/module.cpp @@ -0,0 +1,472 @@ +#include +#include + +#include + +#include + +#include "zygisk.hpp" +#include "module.hpp" + +using namespace std; + +ZygiskModule::ZygiskModule(int id, void *handle, void *entry) + : id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { + // Make sure all pointers are null + memset(&api, 0, sizeof(api)); + api.base.impl = this; + api.base.registerModule = &ZygiskModule::RegisterModuleImpl; +} + +bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { + if (api == nullptr || module == nullptr) + return false; + + long api_version = *module; + // Unsupported version + if (api_version > ZYGISK_API_VERSION) + return false; + + // Set the actual module_abi* + api->base.impl->mod = { module }; + + // Fill in API accordingly with module API version + if (api_version >= 1) { + api->v1.hookJniNativeMethods = hookJniNativeMethods; + api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) { + if (g_ctx) g_ctx->plt_hook_register(a, b, c, d); + }; + api->v1.pltHookExclude = [](auto a, auto b) { + if (g_ctx) g_ctx->plt_hook_exclude(a, b); + }; + api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); }; + api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); }; + api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); }; + } + if (api_version >= 2) { + api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; + api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; + } + if (api_version >= 4) { + api->v4.pltHookCommit = lsplt::CommitHook; + api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) { + if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr) + return; + lsplt::RegisterHook(dev, inode, symbol, fn, backup); + }; + api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); }; + } + + return true; +} + +bool ZygiskModule::valid() const { + if (mod.api_version == nullptr) + return false; + switch (*mod.api_version) { + case 4: + case 3: + case 2: + case 1: + return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize && + mod.v1->preServerSpecialize && mod.v1->postServerSpecialize; + default: + return false; + } +} + +int ZygiskModule::connectCompanion() const { + if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) { + write_int(fd, id); + return fd; + } + return -1; +} + +int ZygiskModule::getModuleDir() const { + if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) { + write_int(fd, id); + int dfd = recv_fd(fd); + close(fd); + return dfd; + } + return -1; +} + +void ZygiskModule::setOption(zygisk::Option opt) { + if (g_ctx == nullptr) + return; + switch (opt) { + case zygisk::FORCE_DENYLIST_UNMOUNT: + g_ctx->flags |= DO_REVERT_UNMOUNT; + break; + case zygisk::DLCLOSE_MODULE_LIBRARY: + unload = true; + break; + } +} + +uint32_t ZygiskModule::getFlags() { + return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0; +} + +void ZygiskModule::tryUnload() const { + if (unload) dlclose(handle); +} + +// ----------------------------------------------------------------- + +#define call_app(method) \ +switch (*mod.api_version) { \ +case 1: \ +case 2: { \ + AppSpecializeArgs_v1 a(args); \ + mod.v1->method(mod.v1->impl, &a); \ + break; \ +} \ +case 3: \ +case 4: \ + mod.v1->method(mod.v1->impl, args);\ + break; \ +} + +void ZygiskModule::preAppSpecialize(AppSpecializeArgs_v3 *args) const { + call_app(preAppSpecialize) +} + +void ZygiskModule::postAppSpecialize(const AppSpecializeArgs_v3 *args) const { + call_app(postAppSpecialize) +} + +void ZygiskModule::preServerSpecialize(ServerSpecializeArgs_v1 *args) const { + mod.v1->preServerSpecialize(mod.v1->impl, args); +} + +void ZygiskModule::postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { + mod.v1->postServerSpecialize(mod.v1->impl, args); +} + +// ----------------------------------------------------------------- + +void ZygiskContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { + if (regex == nullptr || symbol == nullptr || fn == nullptr) + return; + regex_t re; + if (regcomp(&re, regex, REG_NOSUB) != 0) + return; + mutex_guard lock(hook_info_lock); + register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); +} + +void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) { + if (!regex) return; + regex_t re; + if (regcomp(&re, regex, REG_NOSUB) != 0) + return; + mutex_guard lock(hook_info_lock); + ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); +} + +void ZygiskContext::plt_hook_process_regex() { + if (register_info.empty()) + return; + for (auto &map : lsplt::MapInfo::Scan()) { + if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue; + for (auto ®: register_info) { + if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) + continue; + bool ignored = false; + for (auto &ign: ignore_info) { + if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0) + continue; + if (ign.symbol.empty() || ign.symbol == reg.symbol) { + ignored = true; + break; + } + } + if (!ignored) { + lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup); + } + } + } +} + +bool ZygiskContext::plt_hook_commit() { + { + mutex_guard lock(hook_info_lock); + plt_hook_process_regex(); + register_info.clear(); + ignore_info.clear(); + } + return lsplt::CommitHook(); +} + +// ----------------------------------------------------------------- + +void ZygiskContext::sanitize_fds() { + zygisk_close_logd(); + + if (!is_child()) { + return; + } + + if (can_exempt_fd() && !exempted_fds.empty()) { + auto update_fd_array = [&](int old_len) -> jintArray { + jintArray array = env->NewIntArray(static_cast(old_len + exempted_fds.size())); + if (array == nullptr) + return nullptr; + + env->SetIntArrayRegion( + array, old_len, static_cast(exempted_fds.size()), exempted_fds.data()); + for (int fd : exempted_fds) { + if (fd >= 0 && fd < MAX_FD_SIZE) { + allowed_fds[fd] = true; + } + } + *args.app->fds_to_ignore = array; + return array; + }; + + if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { + int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr); + int len = env->GetArrayLength(fdsToIgnore); + for (int i = 0; i < len; ++i) { + int fd = arr[i]; + if (fd >= 0 && fd < MAX_FD_SIZE) { + allowed_fds[fd] = true; + } + } + if (jintArray newFdList = update_fd_array(len)) { + env->SetIntArrayRegion(newFdList, 0, len, arr); + } + env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT); + } else { + update_fd_array(0); + } + } + + // Close all forbidden fds to prevent crashing + auto dir = xopen_dir("/proc/self/fd"); + int dfd = dirfd(dir.get()); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + int fd = parse_int(entry->d_name); + if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) { + close(fd); + } + } +} + +bool ZygiskContext::exempt_fd(int fd) { + if ((flags & POST_SPECIALIZE) || (flags & SKIP_CLOSE_LOG_PIPE)) + return true; + if (!can_exempt_fd()) + return false; + exempted_fds.push_back(fd); + return true; +} + +bool ZygiskContext::can_exempt_fd() const { + return (flags & APP_FORK_AND_SPECIALIZE) && args.app->fds_to_ignore; +} + +static int sigmask(int how, int signum) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signum); + return sigprocmask(how, &set, nullptr); +} + +void ZygiskContext::fork_pre() { + // Do our own fork before loading any 3rd party code + // First block SIGCHLD, unblock after original fork is done + sigmask(SIG_BLOCK, SIGCHLD); + pid = old_fork(); + + if (!is_child()) + return; + + // Record all open fds + auto dir = xopen_dir("/proc/self/fd"); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + int fd = parse_int(entry->d_name); + if (fd < 0 || fd >= MAX_FD_SIZE) { + close(fd); + continue; + } + allowed_fds[fd] = true; + } + // The dirfd will be closed once out of scope + allowed_fds[dirfd(dir.get())] = false; + // logd_fd should be handled separately + if (int fd = zygisk_get_logd(); fd >= 0) { + allowed_fds[fd] = false; + } +} + +void ZygiskContext::fork_post() { + // Unblock SIGCHLD in case the original method didn't + sigmask(SIG_UNBLOCK, SIGCHLD); +} + +void ZygiskContext::run_modules_pre(const vector &fds) { + for (int i = 0; i < fds.size(); ++i) { + struct stat s{}; + if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) { + close(fds[i]); + continue; + } + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fds[i], + }; + if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { + if (void *e = dlsym(h, "zygisk_module_entry")) { + modules.emplace_back(i, h, e); + } + } else if (flags & SERVER_FORK_AND_SPECIALIZE) { + ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror()); + } + close(fds[i]); + } + + for (auto it = modules.begin(); it != modules.end();) { + it->onLoad(env); + if (it->valid()) { + ++it; + } else { + it = modules.erase(it); + } + } + + for (auto &m : modules) { + if (flags & APP_SPECIALIZE) { + m.preAppSpecialize(args.app); + } else if (flags & SERVER_FORK_AND_SPECIALIZE) { + m.preServerSpecialize(args.server); + } + } +} + +void ZygiskContext::run_modules_post() { + flags |= POST_SPECIALIZE; + for (const auto &m : modules) { + if (flags & APP_SPECIALIZE) { + m.postAppSpecialize(args.app); + } else if (flags & SERVER_FORK_AND_SPECIALIZE) { + m.postServerSpecialize(args.server); + } + m.tryUnload(); + } +} + +void ZygiskContext::app_specialize_pre() { + flags |= APP_SPECIALIZE; + + vector module_fds; + int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds); + if (args.app->app_data_dir) { + const auto *app_data_dir = env->GetStringUTFChars(args.app->app_data_dir, nullptr); + if (std::string_view(app_data_dir).ends_with("/com.android.systemui")) { + info_flags |= PROCESS_IS_SYS_UI; + } + env->ReleaseStringUTFChars(args.app->app_data_dir, app_data_dir); + } + if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { + ZLOGI("[%s] is on the denylist\n", process); + flags |= DO_REVERT_UNMOUNT; + } else if (fd >= 0) { + run_modules_pre(module_fds); + } + close(fd); +} + +void ZygiskContext::app_specialize_post() { + run_modules_post(); + if (info_flags & PROCESS_IS_MAGISK_APP) { + setenv("ZYGISK_ENABLED", "1", 1); + } + + // Cleanups + env->ReleaseStringUTFChars(args.app->nice_name, process); +} + +void ZygiskContext::server_specialize_pre() { + vector module_fds; + int fd = remote_get_info(1000, "system_server", &info_flags, module_fds); + if (fd >= 0) { + if (module_fds.empty()) { + write_int(fd, 0); + } else { + run_modules_pre(module_fds); + + // Send the bitset of module status back to magiskd from system_server + dynamic_bitset bits; + for (const auto &m : modules) + bits[m.getId()] = true; + write_int(fd, static_cast(bits.slots())); + for (int i = 0; i < bits.slots(); ++i) { + auto l = bits.get_slot(i); + xwrite(fd, &l, sizeof(l)); + } + } + close(fd); + } +} + +void ZygiskContext::server_specialize_post() { + run_modules_post(); +} + +// ----------------------------------------------------------------- + +void ZygiskContext::nativeSpecializeAppProcess_pre() { + process = env->GetStringUTFChars(args.app->nice_name, nullptr); + ZLOGV("pre specialize [%s]\n", process); + // App specialize does not check FD + flags |= SKIP_CLOSE_LOG_PIPE; + app_specialize_pre(); +} + +void ZygiskContext::nativeSpecializeAppProcess_post() { + ZLOGV("post specialize [%s]\n", process); + app_specialize_post(); +} + +void ZygiskContext::nativeForkSystemServer_pre() { + ZLOGV("pre forkSystemServer\n"); + flags |= SERVER_FORK_AND_SPECIALIZE; + + fork_pre(); + if (is_child()) { + server_specialize_pre(); + } + sanitize_fds(); +} + +void ZygiskContext::nativeForkSystemServer_post() { + if (is_child()) { + ZLOGV("post forkSystemServer\n"); + server_specialize_post(); + } + fork_post(); +} + +void ZygiskContext::nativeForkAndSpecialize_pre() { + process = env->GetStringUTFChars(args.app->nice_name, nullptr); + ZLOGV("pre forkAndSpecialize [%s]\n", process); + flags |= APP_FORK_AND_SPECIALIZE; + + fork_pre(); + if (is_child()) { + app_specialize_pre(); + } + sanitize_fds(); +} + +void ZygiskContext::nativeForkAndSpecialize_post() { + if (is_child()) { + ZLOGV("post forkAndSpecialize [%s]\n", process); + app_specialize_post(); + } + fork_post(); +} diff --git a/native/src/zygisk/module.hpp b/native/src/core/zygisk/module.hpp similarity index 74% rename from native/src/zygisk/module.hpp rename to native/src/core/zygisk/module.hpp index fef9fc141d70..5b0c8e7c7052 100644 --- a/native/src/zygisk/module.hpp +++ b/native/src/core/zygisk/module.hpp @@ -1,10 +1,12 @@ #pragma once -#include "api.hpp" +#include +#include +#include -namespace { +#include "api.hpp" -struct HookContext; +struct ZygiskContext; struct ZygiskModule; struct AppSpecializeArgs_v1; @@ -155,44 +157,23 @@ union ApiTable { api_abi_v4 v4; }; -#define call_app(method) \ -switch (*mod.api_version) { \ -case 1: \ -case 2: { \ - AppSpecializeArgs_v1 a(args); \ - mod.v1->method(mod.v1->impl, &a); \ - break; \ -} \ -case 3: \ -case 4: \ - mod.v1->method(mod.v1->impl, args);\ - break; \ -} - struct ZygiskModule { void onLoad(void *env) { entry.fn(&api, env); } - void preAppSpecialize(AppSpecializeArgs_v3 *args) const { - call_app(preAppSpecialize) - } - void postAppSpecialize(const AppSpecializeArgs_v3 *args) const { - call_app(postAppSpecialize) - } - void preServerSpecialize(ServerSpecializeArgs_v1 *args) const { - mod.v1->preServerSpecialize(mod.v1->impl, args); - } - void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { - mod.v1->postServerSpecialize(mod.v1->impl, args); - } + + void preAppSpecialize(AppSpecializeArgs_v3 *args) const; + void postAppSpecialize(const AppSpecializeArgs_v3 *args) const; + void preServerSpecialize(ServerSpecializeArgs_v1 *args) const; + void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const; bool valid() const; int connectCompanion() const; int getModuleDir() const; void setOption(zygisk::Option opt); static uint32_t getFlags(); - void tryUnload() const { if (unload) dlclose(handle); } + void tryUnload() const; void clearApi() { memset(&api, 0, sizeof(api)); } int getId() const { return id; } @@ -218,4 +199,80 @@ struct ZygiskModule { } mod; }; -} // namespace +extern ZygiskContext *g_ctx; +extern int (*old_fork)(void); + +enum : uint32_t { + POST_SPECIALIZE = (1u << 0), + APP_FORK_AND_SPECIALIZE = (1u << 1), + APP_SPECIALIZE = (1u << 2), + SERVER_FORK_AND_SPECIALIZE = (1u << 3), + DO_REVERT_UNMOUNT = (1u << 4), + SKIP_CLOSE_LOG_PIPE = (1u << 5), +}; + +#define MAX_FD_SIZE 1024 + +#define DCL_PRE_POST(name) \ +void name##_pre(); \ +void name##_post(); + +struct ZygiskContext { + JNIEnv *env; + union { + void *ptr; + AppSpecializeArgs_v3 *app; + ServerSpecializeArgs_v1 *server; + } args; + + const char *process; + std::list modules; + + int pid; + uint32_t flags; + uint32_t info_flags; + std::bitset allowed_fds; + std::vector exempted_fds; + + struct RegisterInfo { + regex_t regex; + std::string symbol; + void *callback; + void **backup; + }; + + struct IgnoreInfo { + regex_t regex; + std::string symbol; + }; + + pthread_mutex_t hook_info_lock; + std::vector register_info; + std::vector ignore_info; + + ZygiskContext(JNIEnv *env, void *args); + ~ZygiskContext(); + + void run_modules_pre(const std::vector &fds); + void run_modules_post(); + DCL_PRE_POST(fork) + DCL_PRE_POST(app_specialize) + DCL_PRE_POST(server_specialize) + DCL_PRE_POST(nativeForkAndSpecialize) + DCL_PRE_POST(nativeSpecializeAppProcess) + DCL_PRE_POST(nativeForkSystemServer) + + void sanitize_fds(); + bool exempt_fd(int fd); + bool can_exempt_fd() const; + bool is_child() const { return pid <= 0; } + + // Compatibility shim + void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup); + void plt_hook_exclude(const char *regex, const char *symbol); + void plt_hook_process_regex(); + + bool plt_hook_commit(); +}; + +#undef DCL_PRE_POST diff --git a/native/src/zygisk/zygisk.hpp b/native/src/core/zygisk/zygisk.hpp similarity index 53% rename from native/src/zygisk/zygisk.hpp rename to native/src/core/zygisk/zygisk.hpp index 58ebffb121f6..702cb2d77b70 100644 --- a/native/src/zygisk/zygisk.hpp +++ b/native/src/core/zygisk/zygisk.hpp @@ -4,21 +4,13 @@ #include #include #include -#include - -#define MAGISKTMP_ENV "MAGISKTMP" - -#define HIJACK_BIN64 "/system/bin/appwidget" -#define HIJACK_BIN32 "/system/bin/bu" +#include namespace ZygiskRequest { enum : int { - SETUP, GET_INFO, - GET_LOG_PIPE, CONNECT_COMPANION, GET_MODDIR, - PASSTHROUGH, END }; } @@ -28,30 +20,22 @@ enum : int { #define ZLOGE(...) LOGE("zygisk64: " __VA_ARGS__) #define ZLOGI(...) LOGI("zygisk64: " __VA_ARGS__) #define ZLOGW(...) LOGW("zygisk64: " __VA_ARGS__) -#define HIJACK_BIN HIJACK_BIN64 #else #define ZLOGD(...) LOGD("zygisk32: " __VA_ARGS__) #define ZLOGE(...) LOGE("zygisk32: " __VA_ARGS__) #define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__) #define ZLOGW(...) LOGW("zygisk32: " __VA_ARGS__) -#define HIJACK_BIN HIJACK_BIN32 #endif -// Unmap all pages matching the name -void unmap_all(const char *name); - -// Remap all matching pages with anonymous pages -void remap_all(const char *name); - -// Get library name + offset (from start of ELF), given function address -uintptr_t get_function_off(int pid, uintptr_t addr, char *lib); - -// Get function address, given library name + offset -uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off); +// Extreme verbose logging +#define ZLOGV(...) ZLOGD(__VA_ARGS__) +//#define ZLOGV(...) (void*)0 extern void *self_handle; void hook_functions(); +void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods); + int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector &fds); inline int zygisk_request(int req) { @@ -60,3 +44,19 @@ inline int zygisk_request(int req) { write_int(fd, req); return fd; } + +// The reference of the following structs +// https://cs.android.com/android/platform/superproject/main/+/main:art/libnativebridge/include/nativebridge/native_bridge.h + +struct NativeBridgeRuntimeCallbacks { + const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid); + uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz); + uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, + uint32_t method_count); +}; + +struct NativeBridgeCallbacks { + uint32_t version; + void *padding[5]; + bool (*isCompatibleWith)(uint32_t); +}; diff --git a/native/src/exported_sym.txt b/native/src/exported_sym.txt index 80dc508b4f17..802ae57bc7c6 100644 --- a/native/src/exported_sym.txt +++ b/native/src/exported_sym.txt @@ -1,4 +1,4 @@ { zygisk_inject_entry; - unload_first_stage; + NativeBridgeItf; }; diff --git a/native/src/external/Android.mk b/native/src/external/Android.mk index 42cdb39ef09e..af88e758e37e 100644 --- a/native/src/external/Android.mk +++ b/native/src/external/Android.mk @@ -1,11 +1,5 @@ LOCAL_PATH := $(call my-dir) -# Header only library -include $(CLEAR_VARS) -LOCAL_MODULE:= libphmap -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap -include $(BUILD_STATIC_LIBRARY) - # libxz.a include $(CLEAR_VARS) LOCAL_MODULE:= libxz diff --git a/native/src/external/parallel-hashmap b/native/src/external/parallel-hashmap deleted file mode 160000 index 7684faf18680..000000000000 --- a/native/src/external/parallel-hashmap +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7684faf186806e2c88554a78188c18185b21f127 diff --git a/native/src/include/magisk.hpp b/native/src/include/consts.hpp similarity index 85% rename from native/src/include/magisk.hpp rename to native/src/include/consts.hpp index 9010aab2ce8b..7df3f96a65ee 100644 --- a/native/src/include/magisk.hpp +++ b/native/src/include/consts.hpp @@ -1,9 +1,8 @@ #pragma once -#include - #define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" -#define LOGFILE "/cache/magisk.log" +#define ZYGISKLDR "libzygisk.so" +#define NBPROP "ro.dalvik.vm.native.bridge" #define SECURE_DIR "/data/adb" #define MODULEROOT SECURE_DIR "/modules" #define MODULEUPGRADE SECURE_DIR "/modules_update" @@ -11,7 +10,6 @@ #define MAGISKDB SECURE_DIR "/magisk.db" // tmpfs paths -extern std::string MAGISKTMP; #define INTLROOT ".magisk" #define MIRRDIR INTLROOT "/mirror" #define PREINITMIRR INTLROOT "/preinit" @@ -23,10 +21,10 @@ extern std::string MAGISKTMP; #define ROOTOVL INTLROOT "/rootdir" #define SHELLPTS INTLROOT "/pts" #define ROOTMNT ROOTOVL "/.mount_list" -#define ZYGISKBIN INTLROOT "/zygisk" #define SELINUXMOCK INTLROOT "/selinux" #define MAIN_CONFIG INTLROOT "/config" #define MAIN_SOCKET INTLROOT "/socket" +#define LOG_PIPE INTLROOT "/log" constexpr const char *applet_names[] = { "su", "resetprop", nullptr }; @@ -39,6 +37,9 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr }; // Unconstrained file type that anyone can access #define SEPOL_FILE_TYPE "magisk_file" #define MAGISK_FILE_CON "u:object_r:" SEPOL_FILE_TYPE ":s0" +// Log pipe that only root and zygote can open +#define SEPOL_LOG_TYPE "magisk_log_file" +#define MAGISK_LOG_CON "u:object_r:" SEPOL_LOG_TYPE ":s0" extern int SDK_INT; #define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") @@ -47,5 +48,4 @@ extern int SDK_INT; int magisk_main(int argc, char *argv[]); int su_client_main(int argc, char *argv[]); int resetprop_main(int argc, char *argv[]); -int app_process_main(int argc, char *argv[]); int zygisk_main(int argc, char *argv[]); diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index a57d0272aa80..8fae0c4bab7e 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -6,3 +6,17 @@ macro_rules! LOGFILE { "/cache/magisk.log" }; } + +#[macro_export] +macro_rules! INTLROOT { + () => { + ".magisk" + }; +} + +#[macro_export] +macro_rules! LOG_PIPE { + () => { + concat!($crate::INTLROOT!(), "/log") + }; +} diff --git a/native/src/init/mount.cpp b/native/src/init/mount.cpp index 04e82dc87f10..476257870ca4 100644 --- a/native/src/init/mount.cpp +++ b/native/src/init/mount.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include "init.hpp" diff --git a/native/src/init/rootdir.cpp b/native/src/init/rootdir.cpp index 79b22b419f23..359c5f169936 100644 --- a/native/src/init/rootdir.cpp +++ b/native/src/init/rootdir.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -13,48 +13,68 @@ using namespace std; static vector rc_list; -static void patch_init_rc(const char *src, const char *dest, const char *tmp_dir) { - FILE *rc = xfopen(dest, "we"); - if (!rc) { - PLOGE("%s: open %s failed", __PRETTY_FUNCTION__, src); - return; - } - file_readline(src, [=](string_view line) -> bool { - // Do not start vaultkeeper - if (str_contains(line, "start vaultkeeper")) { - LOGD("Remove vaultkeeper\n"); - return true; - } - // Do not run flash_recovery - if (str_starts(line, "service flash_recovery")) { - LOGD("Remove flash_recovery\n"); - fprintf(rc, "service flash_recovery /system/bin/xxxxx\n"); - return true; - } - // Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data - if (str_starts(line, "on property:persist.sys.zygote.early=")) { - LOGD("Invalidate persist.sys.zygote.early\n"); - fprintf(rc, "on property:persist.sys.zygote.early.xxxxx=true\n"); +static void patch_rc_scripts(const char *src_path, const char *tmp_path, bool writable) { + auto src_dir = xopen_dir(src_path); + if (!src_dir) return; + int src_fd = dirfd(src_dir.get()); + + // If writable, directly modify the file in src_path, or else add to rootfs overlay + auto dest_dir = writable ? [&] { + return xopen_dir(src_path); + }() : [&] { + char buf[PATH_MAX] = {}; + ssprintf(buf, sizeof(buf), ROOTOVL "%s", src_path); + xmkdirs(buf, 0755); + return xopen_dir(buf); + }(); + if (!dest_dir) return; + int dest_fd = dirfd(dest_dir.get()); + + // First patch init.rc + { + auto src = xopen_file(xopenat(src_fd, "init.rc", O_RDONLY | O_CLOEXEC, 0), "re"); + if (!src) return; + if (writable) unlinkat(src_fd, "init.rc", 0); + auto dest = xopen_file( + xopenat(dest_fd, "init.rc", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we"); + if (!dest) return; + LOGD("Patching init.rc in %s\n", src_path); + file_readline(false, src.get(), [&dest](string_view line) -> bool { + // Do not start vaultkeeper + if (str_contains(line, "start vaultkeeper")) { + LOGD("Remove vaultkeeper\n"); + return true; + } + // Do not run flash_recovery + if (line.starts_with("service flash_recovery")) { + LOGD("Remove flash_recovery\n"); + fprintf(dest.get(), "service flash_recovery /system/bin/true\n"); + return true; + } + // Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data + if (line.starts_with("on property:persist.sys.zygote.early=")) { + LOGD("Invalidate persist.sys.zygote.early\n"); + fprintf(dest.get(), "on property:persist.sys.zygote.early.xxxxx=true\n"); + return true; + } + // Else just write the line + fprintf(dest.get(), "%s", line.data()); return true; - } - // Else just write the line - fprintf(rc, "%s", line.data()); - return true; - }); + }); - fprintf(rc, "\n"); + fprintf(dest.get(), "\n"); - // Inject custom rc scripts - for (auto &script : rc_list) { - // Replace template arguments of rc scripts with dynamic paths - replace_all(script, "${MAGISKTMP}", tmp_dir); - fprintf(rc, "\n%s\n", script.data()); - } - rc_list.clear(); + // Inject custom rc scripts + for (auto &script : rc_list) { + // Replace template arguments of rc scripts with dynamic paths + replace_all(script, "${MAGISKTMP}", tmp_path); + fprintf(dest.get(), "\n%s\n", script.data()); + } + rc_list.clear(); - // Inject Magisk rc scripts - LOGD("Inject magisk rc\n"); - fprintf(rc, R"EOF( + // Inject Magisk rc scripts + LOGD("Inject magisk rc\n"); + fprintf(dest.get(), R"EOF( on post-fs-data start logd exec %2$s 0 0 -- %1$s/magisk --post-fs-data @@ -68,15 +88,37 @@ on nonencrypted on property:sys.boot_completed=1 exec %2$s 0 0 -- %1$s/magisk --boot-complete -on property:init.svc.zygote=restarting - exec %2$s 0 0 -- %1$s/magisk --zygote-restart - on property:init.svc.zygote=stopped exec %2$s 0 0 -- %1$s/magisk --zygote-restart -)EOF", tmp_dir, MAGISK_PROC_CON); +)EOF", tmp_path, MAGISK_PROC_CON); - fclose(rc); - clone_attr(src, dest); + fclone_attr(fileno(src.get()), fileno(dest.get())); + } + + // Then patch init.zygote*.rc + for (dirent *entry; (entry = readdir(src_dir.get()));) { + auto name = std::string_view(entry->d_name); + if (!name.starts_with("init.zygote") || !name.ends_with(".rc")) continue; + auto src = xopen_file(xopenat(src_fd, name.data(), O_RDONLY | O_CLOEXEC, 0), "re"); + if (!src) continue; + if (writable) unlinkat(src_fd, name.data(), 0); + auto dest = xopen_file( + xopenat(dest_fd, name.data(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we"); + if (!dest) continue; + LOGD("Patching %s in %s\n", name.data(), src_path); + file_readline(false, src.get(), [&dest, &tmp_path](string_view line) -> bool { + if (line.starts_with("service zygote ")) { + LOGD("Inject zygote restart\n"); + fprintf(dest.get(), "%s", line.data()); + fprintf(dest.get(), " onrestart exec %2$s 0 0 -- %1$s/magisk --zygote-restart\n", + tmp_path, MAGISK_PROC_CON); + return true; + } + fprintf(dest.get(), "%s", line.data()); + return true; + }); + fclone_attr(fileno(src.get()), fileno(dest.get())); + } } static void load_overlay_rc(const char *overlay) { @@ -202,8 +244,8 @@ void MagiskInit::parse_config_file() { }); } -#define ROOTMIR MIRRDIR "/system_root" -#define NEW_INITRC "/system/etc/init/hw/init.rc" +#define ROOTMIR MIRRDIR "/system_root" +#define NEW_INITRC_DIR "/system/etc/init/hw" void MagiskInit::patch_ro_root() { mount_list.emplace_back("/data"); @@ -259,12 +301,11 @@ void MagiskInit::patch_ro_root() { } // Patch init.rc - if (access(NEW_INITRC, F_OK) == 0) { + if (access(NEW_INITRC_DIR, F_OK) == 0) { // Android 11's new init.rc - xmkdirs(dirname(ROOTOVL NEW_INITRC), 0755); - patch_init_rc(NEW_INITRC, ROOTOVL NEW_INITRC, tmp_dir.data()); + patch_rc_scripts(NEW_INITRC_DIR, tmp_dir.data(), false); } else { - patch_init_rc("/init.rc", ROOTOVL "/init.rc", tmp_dir.data()); + patch_rc_scripts("/", tmp_dir.data(), false); } // Extract magisk @@ -312,8 +353,7 @@ void MagiskInit::patch_rw_root() { rm_rf("/.backup"); // Patch init.rc - patch_init_rc("/init.rc", "/init.p.rc", "/sbin"); - rename("/init.p.rc", "/init.rc"); + patch_rc_scripts("/", "/sbin", true); bool treble; { diff --git a/native/src/init/selinux.cpp b/native/src/init/selinux.cpp index 2eff10411a51..8159205a3ee1 100644 --- a/native/src/init/selinux.cpp +++ b/native/src/init/selinux.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/native/src/init/twostage.cpp b/native/src/init/twostage.cpp index 0e1079709a06..50fc348ac277 100644 --- a/native/src/init/twostage.cpp +++ b/native/src/init/twostage.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/native/src/sepolicy/rules.cpp b/native/src/sepolicy/rules.cpp index 46be64f5fe03..abc85d1b28c0 100644 --- a/native/src/sepolicy/rules.cpp +++ b/native/src/sepolicy/rules.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "policy.hpp" @@ -19,6 +19,8 @@ void sepolicy::magisk_rules() { typeattribute(SEPOL_PROC_DOMAIN, "bluetoothdomain"); type(SEPOL_FILE_TYPE, "file_type"); typeattribute(SEPOL_FILE_TYPE, "mlstrustedobject"); + type(SEPOL_LOG_TYPE, "file_type"); + typeattribute(SEPOL_LOG_TYPE, "mlstrustedobject"); // Make our root domain unconstrained allow(SEPOL_PROC_DOMAIN, ALL, ALL, ALL); @@ -37,10 +39,17 @@ void sepolicy::magisk_rules() { allow(ALL, SEPOL_FILE_TYPE, "lnk_file", ALL); allow(ALL, SEPOL_FILE_TYPE, "sock_file", ALL); - // Allow these processes to access MagiskSU - const char *clients[]{"zygote", "shell", - "system_app", "platform_app", "priv_app", - "untrusted_app", "untrusted_app_all"}; + // Only allow zygote to open log pipe + allow("zygote", SEPOL_LOG_TYPE, "fifo_file", "open"); + allow("zygote", SEPOL_LOG_TYPE, "fifo_file", "read"); + // Allow all processes to output logs + allow("domain", SEPOL_LOG_TYPE, "fifo_file", "write"); + + // Allow these processes to access MagiskSU and output logs + const char *clients[] { + "zygote", "shell", "system_app", "platform_app", + "priv_app", "untrusted_app", "untrusted_app_all" + }; for (auto type: clients) { if (!exists(type)) continue; diff --git a/native/src/zygisk/hook.cpp b/native/src/zygisk/hook.cpp deleted file mode 100644 index 0587c9d3a584..000000000000 --- a/native/src/zygisk/hook.cpp +++ /dev/null @@ -1,900 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include "zygisk.hpp" -#include "memory.hpp" -#include "module.hpp" -#include "deny/deny.hpp" - -using namespace std; -using jni_hook::hash_map; -using jni_hook::tree_map; -using xstring = jni_hook::string; -using rust::MagiskD; -using rust::get_magiskd; - -// Extreme verbose logging -//#define ZLOGV(...) ZLOGD(__VA_ARGS__) -#define ZLOGV(...) (void*)0 - -static void hook_unloader(); -static void unhook_functions(); -static void hook_jni_env(); -static void restore_jni_env(JNIEnv *env); - -namespace { - -enum { - POST_SPECIALIZE, - APP_FORK_AND_SPECIALIZE, - APP_SPECIALIZE, - SERVER_FORK_AND_SPECIALIZE, - DO_REVERT_UNMOUNT, - SKIP_FD_SANITIZATION, - - FLAG_MAX -}; - -#define MAX_FD_SIZE 1024 - -// Global variables -vector> *plt_hook_list; -map, StringCmp> *jni_hook_list; -hash_map>> *jni_method_map; -bool should_unmap_zygisk = false; - -// Current context -HookContext *g_ctx; -bitset *g_allowed_fds = nullptr; -const JNINativeInterface *old_functions = nullptr; -JNINativeInterface *new_functions = nullptr; - -#define DCL_PRE_POST(name) \ -void name##_pre(); \ -void name##_post(); - -struct HookContext { - JNIEnv *env; - union { - void *ptr; - AppSpecializeArgs_v3 *app; - ServerSpecializeArgs_v1 *server; - } args; - const MagiskD &magiskd; - - const char *process; - list modules; - - int pid; - bitset flags; - uint32_t info_flags; - vector exempted_fds; - - struct RegisterInfo { - regex_t regex; - string symbol; - void *callback; - void **backup; - }; - - struct IgnoreInfo { - regex_t regex; - string symbol; - }; - - pthread_mutex_t hook_info_lock; - vector register_info; - vector ignore_info; - - HookContext(JNIEnv *env, void *args) : - env(env), args{args}, magiskd(get_magiskd()), process(nullptr), pid(-1), info_flags(0), - hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { - static bool restored_env = false; - if (!restored_env) { - restore_jni_env(env); - restored_env = true; - } - g_ctx = this; - } - - ~HookContext(); - - void run_modules_pre(const vector &fds); - void run_modules_post(); - DCL_PRE_POST(fork) - DCL_PRE_POST(app_specialize) - DCL_PRE_POST(server_specialize) - DCL_PRE_POST(nativeForkAndSpecialize) - DCL_PRE_POST(nativeSpecializeAppProcess) - DCL_PRE_POST(nativeForkSystemServer) - - void sanitize_fds(); - bool exempt_fd(int fd); - bool is_child() const { return pid <= 0; } - bool can_exempt_fd() const { return flags[APP_FORK_AND_SPECIALIZE] && args.app->fds_to_ignore; } - - // Compatibility shim - void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup); - void plt_hook_exclude(const char *regex, const char *symbol); - void plt_hook_process_regex(); - - bool plt_hook_commit(); -}; - -#undef DCL_PRE_POST - -// ----------------------------------------------------------------- - -#define DCL_HOOK_FUNC(ret, func, ...) \ -ret (*old_##func)(__VA_ARGS__); \ -ret new_##func(__VA_ARGS__) - -DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void *func) { - ZLOGD("androidSetCreateThreadFunc\n"); - hook_jni_env(); - old_androidSetCreateThreadFunc(func); -} - -// Skip actual fork and return cached result if applicable -DCL_HOOK_FUNC(int, fork) { - return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); -} - -// Unmount stuffs in the process's private mount namespace -DCL_HOOK_FUNC(int, unshare, int flags) { - int res = old_unshare(flags); - if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0 && - // For some unknown reason, unmounting app_process in SysUI can break. - // This is reproducible on the official AVD running API 26 and 27. - // Simply avoid doing any unmounts for SysUI to avoid potential issues. - (g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) { - if (g_ctx->flags[DO_REVERT_UNMOUNT]) { - revert_unmount(); - } else { - umount2("/system/bin/app_process64", MNT_DETACH); - umount2("/system/bin/app_process32", MNT_DETACH); - } - // Restore errno back to 0 - errno = 0; - } - return res; -} - -// Sanitize file descriptors to prevent crashing -DCL_HOOK_FUNC(void, android_log_close) { - if (g_ctx == nullptr) { - // Happens during un-managed fork like nativeForkApp, nativeForkUsap - get_magiskd().close_log_pipe(); - } else { - g_ctx->sanitize_fds(); - } - old_android_log_close(); -} - -// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns, -// it will return to our code which has been unmapped, causing segmentation fault. -// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start. -DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { - int res = old_pthread_attr_destroy((pthread_attr_t *)target); - - // Only perform unloading on the main thread - if (gettid() != getpid()) - return res; - - ZLOGV("pthread_attr_destroy\n"); - if (should_unmap_zygisk) { - unhook_functions(); - if (should_unmap_zygisk) { - // Because both `pthread_attr_destroy` and `dlclose` have the same function signature, - // we can use `musttail` to let the compiler reuse our stack frame and thus - // `dlclose` will directly return to the caller of `pthread_attr_destroy`. - [[clang::musttail]] return dlclose(self_handle); - } - } - - return res; -} - -#undef DCL_HOOK_FUNC - -// ----------------------------------------------------------------- - -void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { - auto class_map = jni_method_map->find(clz); - if (class_map == jni_method_map->end()) { - for (int i = 0; i < numMethods; ++i) { - methods[i].fnPtr = nullptr; - } - return; - } - - vector hooks; - for (int i = 0; i < numMethods; ++i) { - auto method_map = class_map->second.find(methods[i].name); - if (method_map != class_map->second.end()) { - auto it = method_map->second.find(methods[i].signature); - if (it != method_map->second.end()) { - // Copy the JNINativeMethod - hooks.push_back(methods[i]); - // Save the original function pointer - methods[i].fnPtr = it->second; - // Do not allow double hook, remove method from map - method_map->second.erase(it); - continue; - } - } - // No matching method found, set fnPtr to null - methods[i].fnPtr = nullptr; - } - - if (hooks.empty()) - return; - - old_functions->RegisterNatives( - env, env->FindClass(clz), hooks.data(), static_cast(hooks.size())); -} - -ZygiskModule::ZygiskModule(int id, void *handle, void *entry) -: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { - // Make sure all pointers are null - memset(&api, 0, sizeof(api)); - api.base.impl = this; - api.base.registerModule = &ZygiskModule::RegisterModuleImpl; -} - -bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { - if (api == nullptr || module == nullptr) - return false; - - long api_version = *module; - // Unsupported version - if (api_version > ZYGISK_API_VERSION) - return false; - - // Set the actual module_abi* - api->base.impl->mod = { module }; - - // Fill in API accordingly with module API version - if (api_version >= 1) { - api->v1.hookJniNativeMethods = hookJniNativeMethods; - api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) { - if (g_ctx) g_ctx->plt_hook_register(a, b, c, d); - }; - api->v1.pltHookExclude = [](auto a, auto b) { - if (g_ctx) g_ctx->plt_hook_exclude(a, b); - }; - api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); }; - api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); }; - api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); }; - } - if (api_version >= 2) { - api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; - api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; - } - if (api_version >= 4) { - api->v4.pltHookCommit = lsplt::CommitHook; - api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) { - if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr) - return; - lsplt::RegisterHook(dev, inode, symbol, fn, backup); - }; - api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); }; - } - - return true; -} - -void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { - if (regex == nullptr || symbol == nullptr || fn == nullptr) - return; - regex_t re; - if (regcomp(&re, regex, REG_NOSUB) != 0) - return; - mutex_guard lock(hook_info_lock); - register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); -} - -void HookContext::plt_hook_exclude(const char *regex, const char *symbol) { - if (!regex) return; - regex_t re; - if (regcomp(&re, regex, REG_NOSUB) != 0) - return; - mutex_guard lock(hook_info_lock); - ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); -} - -void HookContext::plt_hook_process_regex() { - if (register_info.empty()) - return; - for (auto &map : lsplt::MapInfo::Scan()) { - if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue; - for (auto ®: register_info) { - if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) - continue; - bool ignored = false; - for (auto &ign: ignore_info) { - if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0) - continue; - if (ign.symbol.empty() || ign.symbol == reg.symbol) { - ignored = true; - break; - } - } - if (!ignored) { - lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup); - } - } - } -} - -bool HookContext::plt_hook_commit() { - { - mutex_guard lock(hook_info_lock); - plt_hook_process_regex(); - register_info.clear(); - ignore_info.clear(); - } - return lsplt::CommitHook(); -} - -bool ZygiskModule::valid() const { - if (mod.api_version == nullptr) - return false; - switch (*mod.api_version) { - case 4: - case 3: - case 2: - case 1: - return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize && - mod.v1->preServerSpecialize && mod.v1->postServerSpecialize; - default: - return false; - } -} - -int ZygiskModule::connectCompanion() const { - if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) { - write_int(fd, id); - return fd; - } - return -1; -} - -int ZygiskModule::getModuleDir() const { - if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) { - write_int(fd, id); - int dfd = recv_fd(fd); - close(fd); - return dfd; - } - return -1; -} - -void ZygiskModule::setOption(zygisk::Option opt) { - if (g_ctx == nullptr) - return; - switch (opt) { - case zygisk::FORCE_DENYLIST_UNMOUNT: - g_ctx->flags[DO_REVERT_UNMOUNT] = true; - break; - case zygisk::DLCLOSE_MODULE_LIBRARY: - unload = true; - break; - } -} - -uint32_t ZygiskModule::getFlags() { - return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0; -} - -// ----------------------------------------------------------------- - -int sigmask(int how, int signum) { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, signum); - return sigprocmask(how, &set, nullptr); -} - -void HookContext::fork_pre() { - if (g_allowed_fds == nullptr) { - default_new(g_allowed_fds); - - auto &allowed_fds = *g_allowed_fds; - // Record all open fds - auto dir = xopen_dir("/proc/self/fd"); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - int fd = parse_int(entry->d_name); - if (fd < 0 || fd >= MAX_FD_SIZE) { - close(fd); - continue; - } - allowed_fds[fd] = true; - } - // The dirfd will be closed once out of scope - allowed_fds[dirfd(dir.get())] = false; - // logd_fd should be handled separately - if (int logd_fd = magiskd.get_log_pipe(); logd_fd >= 0) { - allowed_fds[logd_fd] = false; - } - } - - // Do our own fork before loading any 3rd party code - // First block SIGCHLD, unblock after original fork is done - sigmask(SIG_BLOCK, SIGCHLD); - pid = old_fork(); -} - -void HookContext::fork_post() { - // Unblock SIGCHLD in case the original method didn't - sigmask(SIG_UNBLOCK, SIGCHLD); -} - -void HookContext::sanitize_fds() { - if (flags[SKIP_FD_SANITIZATION]) - return; - - if (!is_child() || g_allowed_fds == nullptr) { - magiskd.close_log_pipe(); - return; - } - - auto &allowed_fds = *g_allowed_fds; - if (can_exempt_fd()) { - if (int logd_fd = magiskd.get_log_pipe(); logd_fd >= 0) { - exempted_fds.push_back(logd_fd); - } - - auto update_fd_array = [&](int old_len) -> jintArray { - if (exempted_fds.empty()) - return nullptr; - - jintArray array = env->NewIntArray(static_cast(old_len + exempted_fds.size())); - if (array == nullptr) - return nullptr; - - env->SetIntArrayRegion( - array, old_len, static_cast(exempted_fds.size()), exempted_fds.data()); - for (int fd : exempted_fds) { - if (fd >= 0 && fd < MAX_FD_SIZE) { - allowed_fds[fd] = true; - } - } - *args.app->fds_to_ignore = array; - flags[SKIP_FD_SANITIZATION] = true; - return array; - }; - - if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { - int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr); - int len = env->GetArrayLength(fdsToIgnore); - for (int i = 0; i < len; ++i) { - int fd = arr[i]; - if (fd >= 0 && fd < MAX_FD_SIZE) { - allowed_fds[fd] = true; - } - } - if (jintArray newFdList = update_fd_array(len)) { - env->SetIntArrayRegion(newFdList, 0, len, arr); - } - env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT); - } else { - update_fd_array(0); - } - } else { - magiskd.close_log_pipe(); - // Switch to plain old android logging because we cannot talk - // to magiskd to fetch our log pipe afterwards anyways. - android_logging(); - } - - // Close all forbidden fds to prevent crashing - auto dir = xopen_dir("/proc/self/fd"); - int dfd = dirfd(dir.get()); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - int fd = parse_int(entry->d_name); - if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) { - close(fd); - } - } -} - -void HookContext::run_modules_pre(const vector &fds) { - for (int i = 0; i < fds.size(); ++i) { - struct stat s{}; - if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) { - close(fds[i]); - continue; - } - android_dlextinfo info { - .flags = ANDROID_DLEXT_USE_LIBRARY_FD, - .library_fd = fds[i], - }; - if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { - if (void *e = dlsym(h, "zygisk_module_entry")) { - modules.emplace_back(i, h, e); - } - } else if (g_ctx->flags[SERVER_FORK_AND_SPECIALIZE]) { - ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror()); - } - close(fds[i]); - } - - for (auto it = modules.begin(); it != modules.end();) { - it->onLoad(env); - if (it->valid()) { - ++it; - } else { - it = modules.erase(it); - } - } - - for (auto &m : modules) { - if (flags[APP_SPECIALIZE]) { - m.preAppSpecialize(args.app); - } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { - m.preServerSpecialize(args.server); - } - } -} - -void HookContext::run_modules_post() { - flags[POST_SPECIALIZE] = true; - for (const auto &m : modules) { - if (flags[APP_SPECIALIZE]) { - m.postAppSpecialize(args.app); - } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { - m.postServerSpecialize(args.server); - } - m.tryUnload(); - } -} - -void HookContext::app_specialize_pre() { - flags[APP_SPECIALIZE] = true; - - vector module_fds; - int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds); - if (args.app->app_data_dir) { - const auto *app_data_dir = env->GetStringUTFChars(args.app->app_data_dir, nullptr); - if (std::string_view(app_data_dir).ends_with("/com.android.systemui")) { - info_flags |= PROCESS_IS_SYS_UI; - } - env->ReleaseStringUTFChars(args.app->app_data_dir, app_data_dir); - } - if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { - ZLOGI("[%s] is on the denylist\n", process); - flags[DO_REVERT_UNMOUNT] = true; - } else if (fd >= 0) { - run_modules_pre(module_fds); - } - close(fd); -} - -void HookContext::app_specialize_post() { - run_modules_post(); - if (info_flags & PROCESS_IS_MAGISK_APP) { - setenv("ZYGISK_ENABLED", "1", 1); - } - - // Cleanups - env->ReleaseStringUTFChars(args.app->nice_name, process); - magiskd.close_log_pipe(); - android_logging(); -} - -void HookContext::server_specialize_pre() { - vector module_fds; - int fd = remote_get_info(1000, "system_server", &info_flags, module_fds); - if (fd >= 0) { - if (module_fds.empty()) { - write_int(fd, 0); - } else { - run_modules_pre(module_fds); - - // Send the bitset of module status back to magiskd from system_server - dynamic_bitset bits; - for (const auto &m : modules) - bits[m.getId()] = true; - write_int(fd, static_cast(bits.slots())); - for (int i = 0; i < bits.slots(); ++i) { - auto l = bits.get_slot(i); - xwrite(fd, &l, sizeof(l)); - } - } - close(fd); - } -} - -void HookContext::server_specialize_post() { - run_modules_post(); -} - -HookContext::~HookContext() { - // This global pointer points to a variable on the stack. - // Set this to nullptr to prevent leaking local variable. - // This also disables most plt hooked functions. - g_ctx = nullptr; - - if (!is_child()) - return; - - should_unmap_zygisk = true; - - // Unhook JNI methods - for (const auto &[clz, methods] : *jni_hook_list) { - if (!methods.empty() && env->RegisterNatives( - env->FindClass(clz.data()), methods.data(), - static_cast(methods.size())) != 0) { - ZLOGE("Failed to restore JNI hook of class [%s]\n", clz.data()); - should_unmap_zygisk = false; - } - } - delete jni_hook_list; - jni_hook_list = nullptr; - - // Do NOT directly call delete - operator delete(jni_method_map); - // Directly unmap the whole memory block - jni_hook::memory_block::release(); - jni_method_map = nullptr; - - // Strip out all API function pointers - for (auto &m : modules) { - m.clearApi(); - } - - hook_unloader(); -} - -bool HookContext::exempt_fd(int fd) { - if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION]) - return true; - if (!can_exempt_fd()) - return false; - exempted_fds.push_back(fd); - return true; -} - -// ----------------------------------------------------------------- - -void HookContext::nativeSpecializeAppProcess_pre() { - process = env->GetStringUTFChars(args.app->nice_name, nullptr); - ZLOGV("pre specialize [%s]\n", process); - // App specialize does not check FD - flags[SKIP_FD_SANITIZATION] = true; - app_specialize_pre(); -} - -void HookContext::nativeSpecializeAppProcess_post() { - ZLOGV("post specialize [%s]\n", process); - app_specialize_post(); -} - -void HookContext::nativeForkSystemServer_pre() { - ZLOGV("pre forkSystemServer\n"); - flags[SERVER_FORK_AND_SPECIALIZE] = true; - - fork_pre(); - if (is_child()) { - server_specialize_pre(); - } -} - -void HookContext::nativeForkSystemServer_post() { - if (is_child()) { - ZLOGV("post forkSystemServer\n"); - server_specialize_post(); - } - fork_post(); -} - -void HookContext::nativeForkAndSpecialize_pre() { - process = env->GetStringUTFChars(args.app->nice_name, nullptr); - ZLOGV("pre forkAndSpecialize [%s]\n", process); - flags[APP_FORK_AND_SPECIALIZE] = true; - - fork_pre(); - if (is_child()) { - app_specialize_pre(); - } -} - -void HookContext::nativeForkAndSpecialize_post() { - if (is_child()) { - ZLOGV("post forkAndSpecialize [%s]\n", process); - app_specialize_post(); - } - fork_post(); -} - -} // namespace - -// ----------------------------------------------------------------- - -static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) { - if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) { - ZLOGE("Failed to register plt_hook \"%s\"\n", symbol); - return; - } - plt_hook_list->emplace_back(dev, inode, symbol, old_func); -} - -static void hook_commit() { - if (!lsplt::CommitHook()) - ZLOGE("plt_hook failed\n"); -} - -#define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME) \ - hook_register(DEV, INODE, SYM, \ - reinterpret_cast(new_##NAME), reinterpret_cast(&old_##NAME)) - -#define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ - PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) - -void hook_functions() { - default_new(plt_hook_list); - default_new(jni_hook_list); - default_new(jni_method_map); - - ino_t android_runtime_inode = 0; - dev_t android_runtime_dev = 0; - - for (auto &map : lsplt::MapInfo::Scan()) { - if (map.path.ends_with("libandroid_runtime.so")) { - android_runtime_inode = map.inode; - android_runtime_dev = map.dev; - break; - } - } - - PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); - PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare); - PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc); - PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); - hook_commit(); - - // Remove unhooked methods - plt_hook_list->erase( - std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), - [](auto &t) { return *std::get<3>(t) == nullptr;}), - plt_hook_list->end()); -} - -static void hook_unloader() { - ino_t art_inode = 0; - dev_t art_dev = 0; - - for (auto &map : lsplt::MapInfo::Scan()) { - if (map.path.ends_with("/libart.so")) { - art_inode = map.inode; - art_dev = map.dev; - break; - } - } - - PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy); - hook_commit(); -} - -static void unhook_functions() { - // Unhook plt_hook - for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) { - if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) { - ZLOGE("Failed to register plt_hook [%s]\n", sym); - should_unmap_zygisk = false; - } - } - delete plt_hook_list; - plt_hook_list = nullptr; - if (!lsplt::CommitHook()) { - ZLOGE("Failed to restore plt_hook\n"); - should_unmap_zygisk = false; - } -} - -// ----------------------------------------------------------------- - -static JNINativeMethod *hookAndSaveJNIMethods(const char *, const JNINativeMethod *, int); - -static string get_class_name(JNIEnv *env, jclass clazz) { - static auto class_getName = env->GetMethodID( - env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); - auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); - const char *name = env->GetStringUTFChars(nameRef, nullptr); - string className(name); - env->ReleaseStringUTFChars(nameRef, name); - std::replace(className.begin(), className.end(), '.', '/'); - return className; -} - -static jint env_RegisterNatives( - JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { - auto className = get_class_name(env, clazz); - ZLOGV("JNIEnv->RegisterNatives [%s]\n", className.data()); - auto newMethods = unique_ptr( - hookAndSaveJNIMethods(className.data(), methods, numMethods)); - return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); -} - -static void hook_jni_env() { - using method_sig = jint(*)(JavaVM **, jsize, jsize *); - auto get_created_vms = reinterpret_cast( - dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); - if (!get_created_vms) { - for (auto &map: lsplt::MapInfo::Scan()) { - if (!map.path.ends_with("/libnativehelper.so")) continue; - void *h = dlopen(map.path.data(), RTLD_LAZY); - if (!h) { - ZLOGW("Cannot dlopen libnativehelper.so: %s\n", dlerror()); - break; - } - get_created_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); - dlclose(h); - break; - } - if (!get_created_vms) { - ZLOGW("JNI_GetCreatedJavaVMs not found\n"); - return; - } - } - - JavaVM *vm = nullptr; - jsize num = 0; - jint res = get_created_vms(&vm, 1, &num); - if (res != JNI_OK || vm == nullptr) { - ZLOGW("JavaVM not found\n"); - return; - } - JNIEnv *env = nullptr; - res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (res != JNI_OK || env == nullptr) { - ZLOGW("JNIEnv not found\n"); - return; - } - - // Replace the function table in JNIEnv to hook RegisterNatives - default_new(new_functions); - memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &env_RegisterNatives; - old_functions = env->functions; - env->functions = new_functions; -} - -static void restore_jni_env(JNIEnv *env) { - env->functions = old_functions; - delete new_functions; - new_functions = nullptr; -} - -#define HOOK_JNI(method) \ -if (methods[i].name == #method##sv) { \ - int j = 0; \ - for (; j < method##_methods_num; ++j) { \ - if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \ - jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \ - method##_orig = methods[i].fnPtr; \ - newMethods[i] = method##_methods[j]; \ - ZLOGI("replaced %s#" #method "\n", className); \ - --hook_cnt; \ - break; \ - } \ - } \ - if (j == method##_methods_num) { \ - ZLOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \ - } \ - continue; \ -} - -// JNI method hook definitions, auto generated -#include "jni_hooks.hpp" diff --git a/native/src/zygisk/loader.c b/native/src/zygisk/loader.c deleted file mode 100644 index 363d4c70d392..000000000000 --- a/native/src/zygisk/loader.c +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#if defined(__LP64__) -// Use symlink to workaround linker bug on old broken Android -// https://issuetracker.google.com/issues/36914295 -#define SECOND_STAGE_PATH "/system/bin/app_process" -#else -#define SECOND_STAGE_PATH "/system/bin/app_process32" -#endif - -__attribute__((constructor)) -static void zygisk_loader(void) { - android_dlextinfo info = { - .flags = ANDROID_DLEXT_FORCE_LOAD - }; - void *handle = android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info); - if (handle) { - void(*entry)(void*) = dlsym(handle, "zygisk_inject_entry"); - if (entry) { - entry(handle); - } - void (*unload)(void) = dlsym(handle, "unload_first_stage"); - if (unload) { - __attribute__((musttail)) return unload(); - } - } -} diff --git a/native/src/zygisk/main.cpp b/native/src/zygisk/main.cpp deleted file mode 100644 index 85b5967238c2..000000000000 --- a/native/src/zygisk/main.cpp +++ /dev/null @@ -1,208 +0,0 @@ -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "zygisk.hpp" - -using namespace std; - -// Entrypoint for app_process overlay -int app_process_main(int argc, char *argv[]) { - android_logging(); - char buf[PATH_MAX]; - - bool zygote = false; - if (auto fp = open_file("/proc/self/attr/current", "r")) { - fscanf(fp.get(), "%s", buf); - zygote = (buf == "u:r:zygote:s0"sv); - } - - if (!zygote) { - // For the non zygote case, we need to get real app_process via passthrough - // We have to connect magiskd via exec-ing magisk due to SELinux restrictions - - // This is actually only relevant for calling app_process via ADB shell - // because zygisk shall already have the app_process overlays unmounted - // during app process specialization within its private mount namespace. - - int fds[2]; - socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds); - if (fork_dont_care() == 0) { - // This fd has to survive exec - fcntl(fds[1], F_SETFD, 0); - ssprintf(buf, sizeof(buf), "%d", fds[1]); -#if defined(__LP64__) - execlp("magisk", "", "zygisk", "passthrough", buf, "1", (char *) nullptr); -#else - execlp("magisk", "", "zygisk", "passthrough", buf, "0", (char *) nullptr); -#endif - exit(-1); - } - - close(fds[1]); - if (read_int(fds[0]) != 0) { - fprintf(stderr, "Failed to connect magiskd, try umount %s or reboot.\n", argv[0]); - return 1; - } - int app_proc_fd = recv_fd(fds[0]); - if (app_proc_fd < 0) - return 1; - close(fds[0]); - - fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - fexecve(app_proc_fd, argv, environ); - return 1; - } - - if (int socket = zygisk_request(ZygiskRequest::SETUP); socket >= 0) { - do { - if (read_int(socket) != 0) - break; - - // Send over zygisk loader - write_int(socket, sizeof(zygisk_ld)); - xwrite(socket, zygisk_ld, sizeof(zygisk_ld)); - - int app_proc_fd = recv_fd(socket); - if (app_proc_fd < 0) - break; - - string tmp = read_string(socket); - if (char *ld = getenv("LD_PRELOAD")) { - string env = ld; - env += ':'; - env += HIJACK_BIN; - setenv("LD_PRELOAD", env.data(), 1); - } else { - setenv("LD_PRELOAD", HIJACK_BIN, 1); - } - setenv(MAGISKTMP_ENV, tmp.data(), 1); - - close(socket); - - fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - fexecve(app_proc_fd, argv, environ); - } while (false); - - close(socket); - } - - // If encountering any errors, unmount and execute the original app_process - xreadlink("/proc/self/exe", buf, sizeof(buf)); - xumount2("/proc/self/exe", MNT_DETACH); - execve(buf, argv, environ); - return 1; -} - -static void zygiskd(int socket) { - if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) - exit(-1); - -#if defined(__LP64__) - set_nice_name("zygiskd64"); - LOGI("* Launching zygiskd64\n"); -#else - set_nice_name("zygiskd32"); - LOGI("* Launching zygiskd32\n"); -#endif - - // Load modules - using comp_entry = void(*)(int); - vector modules; - { - vector module_fds = recv_fds(socket); - for (int fd : module_fds) { - comp_entry entry = nullptr; - struct stat s{}; - if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) { - android_dlextinfo info { - .flags = ANDROID_DLEXT_USE_LIBRARY_FD, - .library_fd = fd, - }; - if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { - *(void **) &entry = dlsym(h, "zygisk_companion_entry"); - } else { - LOGW("Failed to dlopen zygisk module: %s\n", dlerror()); - } - } - modules.push_back(entry); - close(fd); - } - } - - // ack - write_int(socket, 0); - - // Start accepting requests - pollfd pfd = { socket, POLLIN, 0 }; - for (;;) { - poll(&pfd, 1, -1); - if (pfd.revents && !(pfd.revents & POLLIN)) { - // Something bad happened in magiskd, terminate zygiskd - exit(0); - } - int client = recv_fd(socket); - if (client < 0) { - // Something bad happened in magiskd, terminate zygiskd - exit(0); - } - int module_id = read_int(client); - if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { - exec_task([=, entry = modules[module_id]] { - struct stat s1; - fstat(client, &s1); - entry(client); - // Only close client if it is the same file so we don't - // accidentally close a re-used file descriptor. - // This check is required because the module companion - // handler could've closed the file descriptor already. - if (struct stat s2; fstat(client, &s2) == 0) { - if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { - close(client); - } - } - }); - } else { - close(client); - } - } -} - -// Entrypoint where we need to re-exec ourselves -// This should only ever be called internally -int zygisk_main(int argc, char *argv[]) { - android_logging(); - - if (argc == 3 && argv[1] == "companion"sv) { - zygiskd(parse_int(argv[2])); - } else if (argc == 4 && argv[1] == "passthrough"sv) { - int client = parse_int(argv[2]); - int is_64_bit = parse_int(argv[3]); - if (fcntl(client, F_GETFD) < 0) - return 1; - if (int magiskd = connect_daemon(MainRequest::ZYGISK_PASSTHROUGH); magiskd >= 0) { - write_int(magiskd, ZygiskRequest::PASSTHROUGH); - write_int(magiskd, is_64_bit); - - if (read_int(magiskd) != 0) { - write_int(client, 1); - return 0; - } - - write_int(client, 0); - int real_app_fd = recv_fd(magiskd); - send_fd(client, real_app_fd); - } else { - write_int(client, 1); - return 0; - } - } - return 0; -} diff --git a/native/src/zygisk/memory.cpp b/native/src/zygisk/memory.cpp deleted file mode 100644 index 0b4158fcfdca..000000000000 --- a/native/src/zygisk/memory.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include "memory.hpp" - -namespace jni_hook { - -// We know our minimum alignment is WORD size (size of pointer) -static constexpr size_t ALIGN = sizeof(long); - -// 4MB is more than enough -static constexpr size_t CAPACITY = (1 << 22); - -// No need to be thread safe as the initial mmap always happens on the main thread -static uint8_t *_area = nullptr; - -static std::atomic _curr = nullptr; - -void *memory_block::allocate(size_t sz) { - if (!_area) { - // Memory will not actually be allocated because physical pages are mapped in on-demand - _area = static_cast(xmmap( - nullptr, CAPACITY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - _curr = _area; - } - return _curr.fetch_add(align_to(sz, ALIGN)); -} - -void memory_block::release() { - if (_area) - munmap(_area, CAPACITY); -} - -} // namespace jni_hook diff --git a/native/src/zygisk/memory.hpp b/native/src/zygisk/memory.hpp deleted file mode 100644 index 3c221514884f..000000000000 --- a/native/src/zygisk/memory.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-builtins" -#include -#pragma clang diagnostic pop - -#include - -namespace jni_hook { - -struct memory_block { - static void *allocate(size_t sz); - static void deallocate(void *, size_t) { /* Monotonic increase */ } - static void release(); -}; - -template -using allocator = stateless_allocator; - -using string = std::basic_string, allocator>; - -// Use node_hash_map since it will use less memory because we are using a monotonic allocator -template -using hash_map = phmap::node_hash_map, - phmap::priv::hash_default_eq, - allocator> ->; - -template -using tree_map = std::map, - allocator> ->; - -} // namespace jni_hook - -// Provide heterogeneous lookup for jni_hook::string -namespace phmap::priv { -template <> struct HashEq : StringHashEqT {}; -} // namespace phmap::priv diff --git a/native/src/zygisk/ptrace.cpp b/native/src/zygisk/ptrace.cpp deleted file mode 100644 index ae5bb00edfe1..000000000000 --- a/native/src/zygisk/ptrace.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Original code: https://github.com/Chainfire/injectvm-binderjack/blob/master/app/src/main/jni/libinject/inject.cpp - * The code is heavily modified and sublicensed to GPLv3 for incorporating into Magisk. - * - * Copyright (c) 2015, Simone 'evilsocket' Margaritelli - * Copyright (c) 2015-2019, Jorrit 'Chainfire' Jongma - * Copyright (c) 2021, John 'topjohnwu' Wu - * - * See original LICENSE file from the original project for additional details: - * https://github.com/Chainfire/injectvm-binderjack/blob/master/LICENSE - */ - -/* - * NOTE: - * The code in this file was originally planned to be used for some features, - * but it turned out to be unsuitable for the task. However, this shall remain - * in our arsenal in case it may be used in the future. - */ - -#include -#include -#include -#include - -#include - -#include "zygisk.hpp" -#include "ptrace.hpp" - -using namespace std; - -#if defined(__arm__) -#define CPSR_T_MASK (1u << 5) -#define PARAMS_IN_REGS 4 -#elif defined(__aarch64__) -#define CPSR_T_MASK (1u << 5) -#define PARAMS_IN_REGS 8 -#define pt_regs user_pt_regs -#define uregs regs -#define ARM_pc pc -#define ARM_sp sp -#define ARM_cpsr pstate -#define ARM_lr regs[30] -#define ARM_r0 regs[0] -#endif - -bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) { - for (size_t i = 0; i < len; i += sizeof(long)) { - long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast(addr + i)); - if (data < 0) - return false; - memcpy(static_cast(buf) + i, &data, std::min(len - i, sizeof(data))); - } - return true; -} - -bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len) { - for (size_t i = 0; i < len; i += sizeof(long)) { - long data = 0; - memcpy(&data, static_cast(buf) + i, std::min(len - i, sizeof(data))); - if (xptrace(PTRACE_POKETEXT, pid, reinterpret_cast(addr + i), data) < 0) - return false; - } - return true; -} - -// Get remote registers -#define remote_getregs(regs) _remote_getregs(pid, regs) -static void _remote_getregs(int pid, pt_regs *regs) { -#if defined(__LP64__) - uintptr_t regset = NT_PRSTATUS; - iovec iov{}; - iov.iov_base = regs; - iov.iov_len = sizeof(*regs); - xptrace(PTRACE_GETREGSET, pid, reinterpret_cast(regset), &iov); -#else - xptrace(PTRACE_GETREGS, pid, nullptr, regs); -#endif -} - -// Set remote registers -#define remote_setregs(regs) _remote_setregs(pid, regs) -static void _remote_setregs(int pid, pt_regs *regs) { -#if defined(__LP64__) - uintptr_t regset = NT_PRSTATUS; - iovec iov{}; - iov.iov_base = regs; - iov.iov_len = sizeof(*regs); - xptrace(PTRACE_SETREGSET, pid, reinterpret_cast(regset), &iov); -#else - xptrace(PTRACE_SETREGS, pid, nullptr, regs); -#endif -} - -uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) { - pt_regs regs, regs_bak; - - // Get registers and save a backup - remote_getregs(®s); - memcpy(®s_bak, ®s, sizeof(regs)); - - // ABI dependent: Setup stack and registers to perform the call - -#if defined(__arm__) || defined(__aarch64__) - // Fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters - for (int i = 0; (i < nargs) && (i < PARAMS_IN_REGS); ++i) { - regs.uregs[i] = va_arg(va, uintptr_t); - } - - // Push remaining parameters onto stack - if (nargs > PARAMS_IN_REGS) { - regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS); - uintptr_t stack = regs.ARM_sp; - for (int i = PARAMS_IN_REGS; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - } - - // Set return address - regs.ARM_lr = 0; - - // Set function address to call - regs.ARM_pc = func_addr; - - // Setup the current processor status register - if (regs.ARM_pc & 1u) { - // thumb - regs.ARM_pc &= (~1u); - regs.ARM_cpsr |= CPSR_T_MASK; - } else { - // arm - regs.ARM_cpsr &= ~CPSR_T_MASK; - } -#elif defined(__i386__) - // Push all params onto stack - regs.esp -= sizeof(uintptr_t) * nargs; - uintptr_t stack = regs.esp; - for (int i = 0; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - - // Push return address onto stack - uintptr_t ret_addr = 0; - regs.esp -= sizeof(uintptr_t); - remote_write(regs.esp, &ret_addr, sizeof(uintptr_t)); - - // Set function address to call - regs.eip = func_addr; -#elif defined(__x86_64__) - // Align, rsp - 8 must be a multiple of 16 at function entry point - uintptr_t space = sizeof(uintptr_t); - if (nargs > 6) - space += sizeof(uintptr_t) * (nargs - 6); - while (((regs.rsp - space - 8) & 0xF) != 0) - regs.rsp--; - - // Fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters - for (int i = 0; (i < nargs) && (i < 6); ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - switch (i) { - case 0: regs.rdi = arg; break; - case 1: regs.rsi = arg; break; - case 2: regs.rdx = arg; break; - case 3: regs.rcx = arg; break; - case 4: regs.r8 = arg; break; - case 5: regs.r9 = arg; break; - } - } - - // Push remaining parameters onto stack - if (nargs > 6) { - regs.rsp -= sizeof(uintptr_t) * (nargs - 6); - uintptr_t stack = regs.rsp; - for(int i = 6; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - } - - // Push return address onto stack - uintptr_t ret_addr = 0; - regs.rsp -= sizeof(uintptr_t); - remote_write(regs.rsp, &ret_addr, sizeof(uintptr_t)); - - // Set function address to call - regs.rip = func_addr; - - // may be needed - regs.rax = 0; - regs.orig_rax = 0; -#else -#error Unsupported ABI -#endif - - // Resume process to do the call - remote_setregs(®s); - xptrace(PTRACE_CONT, pid); - - // Catch SIGSEGV caused by the 0 return address - int status; - while (waitpid(pid, &status, __WALL | __WNOTHREAD) == pid) { - if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV)) - break; - xptrace(PTRACE_CONT, pid); - } - - // Get registers again for return value - remote_getregs(®s); - - // Restore registers - remote_setregs(®s_bak); - -#if defined(__arm__) || defined(__aarch64__) - return regs.ARM_r0; -#elif defined(__i386__) - return regs.eax; -#elif defined(__x86_64__) - return regs.rax; -#endif -} - -uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) { - char lib_name[4096]; - auto off = get_function_off(getpid(), addr, lib_name); - if (off == 0) - return 0; - auto remote = get_function_addr(pid, lib_name, off); - if (remote == 0) - return 0; - va_list va; - va_start(va, nargs); - auto result = remote_call_abi(pid, remote, nargs, va); - va_end(va); - return result; -} diff --git a/native/src/zygisk/ptrace.hpp b/native/src/zygisk/ptrace.hpp deleted file mode 100644 index b4951a62502f..000000000000 --- a/native/src/zygisk/ptrace.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -// Write bytes to the remote process at addr -bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len); -#define remote_write(...) _remote_write(pid, __VA_ARGS__) - -// Read bytes from the remote process at addr -bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len); -#define remote_read(...) _remote_read(pid, __VA_ARGS__) - -// Call a remote function -// Arguments are expected to be only integer-like or pointer types -// as other more complex C ABIs are not implemented. -uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va); - -// Find remote offset and invoke function -uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...); - -// C++ wrapper for auto argument counting and casting function pointers -template -static uintptr_t _remote_call(int pid, FuncPtr sym, Args && ...args) { - auto addr = reinterpret_cast(sym); - return remote_call_vararg(pid, addr, sizeof...(args), std::forward(args)...); -} -#define remote_call(...) _remote_call(pid, __VA_ARGS__) diff --git a/native/src/zygisk/utils.cpp b/native/src/zygisk/utils.cpp deleted file mode 100644 index 97cc5e2a389f..000000000000 --- a/native/src/zygisk/utils.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include - -#include "zygisk.hpp" -#include - -using namespace std; -static vector find_maps(const char *name) { - auto maps = lsplt::MapInfo::Scan(); - for (auto iter = maps.begin(); iter != maps.end();) { - if (iter->path != name) { - iter = maps.erase(iter); - } else { - ++iter; - } - } - return maps; -} - -void unmap_all(const char *name) { - auto maps = find_maps(name); - for (auto &info : maps) { - void *addr = reinterpret_cast(info.start); - size_t size = info.end - info.start; - if (info.perms & PROT_READ) { - // Make sure readable pages are still readable - void *dummy = xmmap(nullptr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - mremap(dummy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); - } else { - munmap(addr, size); - } - } -} - -void remap_all(const char *name) { - auto maps = find_maps(name); - for (auto &info : maps) { - void *addr = reinterpret_cast(info.start); - size_t size = info.end - info.start; - void *copy = xmmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if ((info.perms & PROT_READ) == 0) { - mprotect(addr, size, PROT_READ); - } - memcpy(copy, addr, size); - mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); - mprotect(addr, size, info.perms); - } -} - -uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) { - for (auto &info : lsplt::MapInfo::Scan()) { - if (addr >= info.start && addr < info.end) { - if (lib) - strcpy(lib, info.path.data()); - return addr - info.start + info.offset; - } - } - return 0; -} - -uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) { - for (auto &info : lsplt::MapInfo::Scan()) { - if (info.path == lib && (info.perms & PROT_EXEC)) { - return info.start - info.offset + off; - } - } - return 0; -} diff --git a/scripts/avd_magisk.sh b/scripts/avd_magisk.sh index c76ec44b2669..2b6ba4702bc2 100755 --- a/scripts/avd_magisk.sh +++ b/scripts/avd_magisk.sh @@ -61,9 +61,8 @@ done # Stop zygote (and previous setup if exists) magisk --stop 2>/dev/null stop -if [ -d /dev/avd-magisk ]; then - umount -l /dev/avd-magisk 2>/dev/null - rm -rf /dev/avd-magisk 2>/dev/null +if [ -d /debug_ramdisk ]; then + umount -l /debug_ramdisk 2>/dev/null fi # Make sure boot completed props are not set to 1 @@ -92,7 +91,7 @@ elif [ -e /sbin ]; then mount_sbin mkdir -p /dev/sysroot block=$(mount | grep ' / ' | awk '{ print $1 }') - [ $block = "/dev/root" ] && block=/dev/block/dm-0 + [ $block = "/dev/root" ] && block=/dev/block/vda1 mount -o ro $block /dev/sysroot for file in /dev/sysroot/sbin/*; do [ ! -e $file ] && break @@ -108,11 +107,10 @@ elif [ -e /sbin ]; then rm -rf /dev/sysroot else # Android Q+ without sbin - MAGISKTMP=/dev/avd-magisk - mkdir /dev/avd-magisk + MAGISKTMP=/debug_ramdisk # If a file name 'magisk' is in current directory, mount will fail rm -f magisk - mount -t tmpfs -o 'mode=0755' magisk /dev/avd-magisk + mount -t tmpfs -o 'mode=0755' magisk /debug_ramdisk fi # Magisk stuff @@ -138,7 +136,6 @@ else fi ln -s ./magisk $MAGISKTMP/su ln -s ./magisk $MAGISKTMP/resetprop -ln -s ./magisk $MAGISKTMP/magiskhide ln -s ./magiskpolicy $MAGISKTMP/supolicy mkdir -p $MAGISKTMP/.magisk/mirror