improve: mounting system, compatibility; remove: logging on release (#111)

This commit adds numerous improvements to the state of hidden'ility of ReZygisk, and also for compatibility. Recommended to check #111 for more information.
This commit is contained in:
Pedro.js
2025-03-29 12:17:57 -03:00
committed by GitHub
parent 9aafc279d5
commit 886e2f8396
20 changed files with 621 additions and 412 deletions

View File

@@ -63,19 +63,6 @@ namespace zygiskd {
return true;
}
int RequestLogcatFd() {
int fd = Connect(1);
if (fd == -1) {
PLOGE("RequestLogcatFd");
return -1;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd);
return fd;
}
uint32_t GetProcessFlags(uid_t uid) {
int fd = Connect(1);
if (fd == -1) {
@@ -94,8 +81,8 @@ namespace zygiskd {
return res;
}
std::vector<Module> ReadModules() {
std::vector<Module> modules;
std::vector<ModuleInfo> ReadModules() {
std::vector<ModuleInfo> modules;
int fd = Connect(1);
if (fd == -1) {
PLOGE("ReadModules");
@@ -260,4 +247,34 @@ namespace zygiskd {
close(fd);
} else info->running = false;
}
std::string UpdateMountNamespace(enum mount_namespace_state nms_state) {
int fd = Connect(1);
if (fd == -1) {
PLOGE("UpdateMountNamespace");
return "";
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::UpdateMountNamespace);
socket_utils::write_u32(fd, getpid());
socket_utils::write_u8(fd, (uint8_t)nms_state);
uint32_t target_pid = socket_utils::read_u32(fd);
int target_fd = 0;
if (target_pid == 0) goto error;
target_fd = (int)socket_utils::read_u32(fd);
if (target_fd == 0) goto error;
close(fd);
return "/proc/" + std::to_string(target_pid) + "/fd/" + std::to_string(target_fd);
error:
close(fd);
return "";
}
}

View File

@@ -46,9 +46,14 @@ void* DlopenExt(const char* path, int flags) {
}
void* DlopenMem(int fd, int flags) {
auto info = android_dlextinfo{
auto info = android_dlextinfo {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd
.reserved_addr = NULL,
.reserved_size = 0,
.relro_fd = 0,
.library_fd = fd,
.library_fd_offset = 0,
.library_namespace = NULL
};
/* INFO: We need to find the path of the fd since passing "" to android_dlopen_ext

View File

@@ -187,7 +187,7 @@ ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const {
}
}
std::string_view ElfImg::LinearLookupByPrefix(std::string_view name) const {
ElfW(Addr) ElfImg::LinearLookupByPrefix(std::string_view name) const {
if (symtabs_.empty()) {
symtabs_.reserve(symtab_count);
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
@@ -207,11 +207,11 @@ std::string_view ElfImg::LinearLookupByPrefix(std::string_view name) const {
if (symtab.first.size() < size) continue;
if (symtab.first.substr(0, size) == name) {
return symtab.first;
return symtab.second->st_value;
}
}
return "";
return 0;
}

View File

@@ -1,36 +0,0 @@
#include <android/log.h>
#include <unistd.h>
#include "logging.h"
#include "socket_utils.h"
namespace logging {
static int logfd = -1;
void setfd(int fd) {
close(logfd);
logfd = fd;
}
int getfd() {
return logfd;
}
void log(int prio, const char* tag, const char* fmt, ...) {
if (logfd == -1) {
va_list ap;
va_start(ap, fmt);
__android_log_vprint(prio, tag, fmt, ap);
va_end(ap);
} else {
char buf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
socket_utils::write_u8(logfd, prio);
socket_utils::write_string(logfd, tag);
socket_utils::write_string(logfd, buf);
}
}
}

View File

@@ -61,18 +61,23 @@ struct zygote_info {
bool running;
};
enum mount_namespace_state {
Clean,
Rooted,
Module
};
namespace zygiskd {
struct Module {
struct ModuleInfo {
std::string name;
UniqueFd memfd;
inline explicit Module(std::string name, int memfd) : name(name), memfd(memfd) {}
inline explicit ModuleInfo(std::string name, int memfd) : name(name), memfd(memfd) {}
};
enum class SocketAction {
PingHeartBeat,
RequestLogcatFd,
GetProcessFlags,
GetInfo,
ReadModules,
@@ -80,6 +85,7 @@ namespace zygiskd {
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
UpdateMountNamespace
};
void Init(const char *path);
@@ -88,9 +94,7 @@ namespace zygiskd {
bool PingHeartbeat();
int RequestLogcatFd();
std::vector<Module> ReadModules();
std::vector<ModuleInfo> ReadModules();
uint32_t GetProcessFlags(uid_t uid);
@@ -103,4 +107,6 @@ namespace zygiskd {
void SystemServerStarted();
void GetInfo(struct zygote_info *info);
std::string UpdateMountNamespace(enum mount_namespace_state mns_state);
}

View File

@@ -48,8 +48,13 @@ namespace SandHook {
}
}
std::string_view findSymbolNameByPrefix(std::string_view prefix) const {
return LinearLookupByPrefix(prefix);
constexpr ElfW(Addr) getSymbAddressByPrefix(std::string_view prefix) const {
ElfW(Addr) offset = LinearLookupByPrefix(prefix);
if (offset > 0 && base != nullptr) {
return static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias);
} else {
return 0;
}
}
template<typename T>
@@ -57,6 +62,11 @@ namespace SandHook {
return reinterpret_cast<T>(getSymbAddress(name));
}
template<typename T>
constexpr T getSymbAddressByPrefix(std::string_view prefix) const {
return reinterpret_cast<T>(getSymbAddressByPrefix(prefix));
}
bool isValid() const {
return base != nullptr;
}
@@ -76,7 +86,7 @@ namespace SandHook {
ElfW(Addr) LinearLookup(std::string_view name) const;
std::string_view LinearLookupByPrefix(std::string_view name) const;
ElfW(Addr) LinearLookupByPrefix(std::string_view name) const;
constexpr static uint32_t ElfHash(std::string_view name);

View File

@@ -1,35 +1,30 @@
#pragma once
#ifndef LOGGING_H
#define LOGGING_H
#include <android/log.h>
#include <errno.h>
#include <string.h>
#ifndef LOG_TAG
#if defined(__LP64__)
# define LOG_TAG "zygisk-core64"
#else
# define LOG_TAG "zygisk-core32"
#endif
#ifdef __LP64__
#define LOG_TAG "zygisk-core64"
#else
#define LOG_TAG "zygisk-core32"
#endif
#endif
#ifndef NDEBUG
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...)
#define LOGV(...)
#define LOGD(...)
#define LOGV(...)
#endif
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
namespace logging {
void setfd(int fd);
int getfd();
[[gnu::format(printf, 3, 4)]]
void log(int prio, const char* tag, const char* fmt, ...);
}
#endif /* LOGGING_H */

View File

@@ -21,7 +21,6 @@ namespace SoList {
#endif
inline static const char *(*get_realpath_sym)(SoInfo *) = NULL;
inline static const char *(*get_soname_sym)(SoInfo *) = NULL;
inline static void (*soinfo_free)(SoInfo *) = NULL;
inline SoInfo *get_next() {
@@ -38,12 +37,6 @@ namespace SoList {
return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str();
}
inline const char *get_name() {
if (get_soname_sym) return get_soname_sym(this);
return ((std::string *) ((uintptr_t) this + solist_realpath_offset - sizeof(void *)))->c_str();
}
void set_next(SoInfo *si) {
*(SoInfo **) ((uintptr_t) this + solist_next_offset) = si;
}
@@ -110,6 +103,13 @@ namespace SoList {
return addr == NULL ? NULL : *addr;
}
template<typename T>
inline T *getStaticPointerByPrefix(const SandHook::ElfImg &linker, const char *name) {
auto *addr = reinterpret_cast<T **>(linker.getSymbAddressByPrefix(name));
return addr == NULL ? NULL : *addr;
}
static bool DropSoPath(const char* target_path) {
bool path_found = false;
if (solist == NULL && !Initialize()) {
@@ -117,9 +117,9 @@ namespace SoList {
return path_found;
}
for (auto iter = solist; iter; iter = iter->get_next()) {
if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_path)) {
if (iter->get_path() && strstr(iter->get_path(), target_path)) {
SoList::ProtectedDataGuard guard;
LOGI("dropping solist record for %s loaded at %s with size %zu", iter->get_name(), iter->get_path(), iter->get_size());
LOGV("dropping solist record loaded at %s with size %zu", iter->get_path(), iter->get_size());
if (iter->get_size() > 0) {
iter->set_size(0);
SoInfo::soinfo_free(iter);
@@ -136,7 +136,7 @@ namespace SoList {
return;
}
if (g_module_load_counter == NULL || g_module_unload_counter == NULL) {
LOGI("g_module counters not defined, skip reseting them");
LOGD("g_module counters not defined, skip reseting them");
return;
}
auto loaded_modules = *g_module_load_counter;
@@ -163,57 +163,26 @@ namespace SoList {
See #63 for more information.
*/
std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist");
if (solist_sym_name.empty()) return false;
LOGD("found symbol name %s", solist_sym_name.data());
std::string_view soinfo_free_name = linker.findSymbolNameByPrefix("__dl__ZL11soinfo_freeP6soinfo");
if (soinfo_free_name.empty()) return false;
LOGD("found symbol name %s", soinfo_free_name.data());
/* INFO: The size isn't a magic number, it's the size for the string: .llvm.7690929523238822858 */
char llvm_sufix[25 + 1];
if (solist_sym_name.length() != strlen("__dl__ZL6solist")) {
strncpy(llvm_sufix, solist_sym_name.data() + strlen("__dl__ZL6solist"), sizeof(llvm_sufix));
} else {
llvm_sufix[0] = '\0';
}
solist = getStaticPointer<SoInfo>(linker, solist_sym_name.data());
solist = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL6solist");
if (solist == NULL) return false;
LOGD("found symbol solist");
char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)];
snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix);
char sonext_sym_name[sizeof("__dl__ZL6sonext") + sizeof(llvm_sufix)];
snprintf(sonext_sym_name, sizeof(somain_sym_name), "__dl__ZL6sonext%s", llvm_sufix);
char vdso_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)];
snprintf(vdso_sym_name, sizeof(vdso_sym_name), "__dl__ZL4vdso%s", llvm_sufix);
somain = getStaticPointer<SoInfo>(linker, somain_sym_name);
somain = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL6somain");
if (somain == NULL) return false;
LOGD("found symbol somain");
sonext = linker.getSymbAddress<SoInfo **>(sonext_sym_name);
sonext = linker.getSymbAddressByPrefix<SoInfo **>("__dl__ZL6sonext");
if (sonext == NULL) return false;
LOGD("found symbol sonext");
SoInfo *vdso = getStaticPointer<SoInfo>(linker, vdso_sym_name);
SoInfo *vdso = getStaticPointerByPrefix<SoInfo>(linker, "__dl__ZL4vdso");
if (vdso != NULL) LOGD("found symbol vdso");
SoInfo::get_realpath_sym = reinterpret_cast<decltype(SoInfo::get_realpath_sym)>(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv"));
if (SoInfo::get_realpath_sym == NULL) return false;
LOGD("found symbol get_realpath_sym");
SoInfo::get_soname_sym = reinterpret_cast<decltype(SoInfo::get_soname_sym)>(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv"));
if (SoInfo::get_soname_sym == NULL) return false;
LOGD("found symbol get_soname_sym");
SoInfo::soinfo_free = reinterpret_cast<decltype(SoInfo::soinfo_free)>(linker.getSymbAddress(soinfo_free_name));
SoInfo::soinfo_free = reinterpret_cast<decltype(SoInfo::soinfo_free)>(linker.getSymbAddressByPrefix("__dl__ZL11soinfo_freeP6soinfo"));
if (SoInfo::soinfo_free == NULL) return false;
LOGD("found symbol soinfo_free");

View File

@@ -9,7 +9,7 @@ size_t block_size = 0;
extern "C" [[gnu::visibility("default")]]
void entry(void* addr, size_t size, const char* path) {
LOGI("Zygisk library injected, version %s", ZKSU_VERSION);
LOGD("Zygisk library injected, version %s", ZKSU_VERSION);
start_addr = addr;
block_size = size;
zygiskd::Init(path);
@@ -19,11 +19,7 @@ void entry(void* addr, size_t size, const char* path) {
return;
}
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
LOGI("start plt hooking");
LOGD("start plt hooking");
hook_functions();
clean_trace(path, 1, 0, false);
}

View File

@@ -137,6 +137,36 @@ DCL_HOOK_FUNC(int, fork) {
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
}
bool update_mnt_ns(enum mount_namespace_state mns_state, bool dry_run) {
std::string ns_path = zygiskd::UpdateMountNamespace(mns_state);
if (ns_path.empty()) {
PLOGE("Failed to update mount namespace");
return false;
}
if (dry_run) return true;
int updated_ns = open(ns_path.data(), O_RDONLY);
if (updated_ns == -1) {
PLOGE("Failed to open mount namespace [%s]", ns_path.data());
return false;
}
LOGD("set mount namespace to [%s] fd=[%d]\n", ns_path.data(), updated_ns);
if (setns(updated_ns, CLONE_NEWNS) == -1) {
PLOGE("Failed to set mount namespace [%s]", ns_path.data());
close(updated_ns);
return false;
}
close(updated_ns);
return true;
}
// Unmount stuffs in the process's private mount namespace
DCL_HOOK_FUNC(int, unshare, int flags) {
int res = old_unshare(flags);
@@ -144,37 +174,22 @@ DCL_HOOK_FUNC(int, unshare, int flags) {
// 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]) {
if (g_ctx->info_flags & PROCESS_ROOT_IS_KSU) {
revert_unmount_ksu();
} else if (g_ctx->info_flags & PROCESS_ROOT_IS_APATCH){
revert_unmount_apatch();
} else if (g_ctx->info_flags & PROCESS_ROOT_IS_MAGISK) {
revert_unmount_magisk();
}
!g_ctx->flags[SERVER_FORK_AND_SPECIALIZE] && !(g_ctx->info_flags & PROCESS_IS_FIRST_STARTED)) {
if (g_ctx->info_flags & (PROCESS_IS_MANAGER | PROCESS_GRANTED_ROOT)) {
update_mnt_ns(Rooted, false);
} else if (!(g_ctx->flags[DO_REVERT_UNMOUNT])) {
update_mnt_ns(Module, false);
}
/* Zygisksu changed: No umount app_process */
// Restore errno back to 0
errno = 0;
old_unshare(CLONE_NEWNS);
}
/* INFO: To spoof the errno value */
errno = 0;
return res;
}
// Close logd_fd if necessary to prevent crashing
// For more info, check comments in zygisk_log_write
DCL_HOOK_FUNC(void, android_log_close) {
if (g_ctx == nullptr) {
// Happens during un-managed fork like nativeForkApp, nativeForkUsap
logging::setfd(-1);
} else if (!g_ctx->flags[SKIP_FD_SANITIZATION]) {
logging::setfd(-1);
}
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_setstacksize` which will be called when VM daemon threads start.
@@ -189,11 +204,13 @@ DCL_HOOK_FUNC(int, pthread_attr_setstacksize, void *target, size_t size) {
if (should_unmap_zygisk) {
unhook_functions();
cached_map_infos.clear();
if (should_unmap_zygisk) {
// Because both `pthread_attr_setstacksize` 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_setstacksize`.
LOGI("unmap libzygisk.so loaded at %p with size %zu", start_addr, block_size);
LOGD("unmap libzygisk.so loaded at %p with size %zu", start_addr, block_size);
[[clang::musttail]] return munmap(start_addr, block_size);
}
}
@@ -598,14 +615,18 @@ void ZygiskContext::run_modules_post() {
/* Zygisksu changed: Load module fds */
void ZygiskContext::app_specialize_pre() {
flags[APP_SPECIALIZE] = true;
info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid);
if (info_flags & PROCESS_IS_FIRST_STARTED) {
update_mnt_ns(Clean, true);
}
if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) {
flags[DO_REVERT_UNMOUNT] = true;
flags[DO_REVERT_UNMOUNT] = true;
}
if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) {
LOGI("Manager process detected. Notifying that Zygisk has been enabled.");
LOGD("Manager process detected. Notifying that Zygisk has been enabled.");
setenv("ZYGISK_ENABLED", "1", 1);
} else {
@@ -620,7 +641,6 @@ void ZygiskContext::app_specialize_post() {
// Cleanups
env->ReleaseStringUTFChars(args.app->nice_name, process);
g_ctx = nullptr;
logging::setfd(-1);
}
bool ZygiskContext::exempt_fd(int fd) {
@@ -653,11 +673,10 @@ void ZygiskContext::nativeForkSystemServer_pre() {
flags[SERVER_FORK_AND_SPECIALIZE] = true;
fork_pre();
if (pid != 0)
return;
run_modules_pre();
zygiskd::SystemServerStarted();
if (is_child()) {
run_modules_pre();
zygiskd::SystemServerStarted();
}
sanitize_fds();
}
@@ -673,12 +692,9 @@ void ZygiskContext::nativeForkSystemServer_post() {
void ZygiskContext::nativeForkAndSpecialize_pre() {
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
LOGV("pre forkAndSpecialize [%s]", process);
flags[APP_FORK_AND_SPECIALIZE] = true;
/* Zygisksu changed: No args.app->fds_to_ignore check since we are Android 10+ */
if (logging::getfd() != -1) {
exempted_fds.push_back(logging::getfd());
}
update_mnt_ns(Clean, false);
fork_pre();
if (pid == 0) {
@@ -805,7 +821,6 @@ void hook_functions() {
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, strdup);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit();
// Remove unhooked methods

View File

@@ -126,13 +126,13 @@ namespace {
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
PROCESS_IS_MANAGER = (1u << 28),
PROCESS_ROOT_IS_APATCH = (1u << 27),
PROCESS_IS_MANAGER = (1u << 27),
PROCESS_ROOT_IS_APATCH = (1u << 28),
PROCESS_ROOT_IS_KSU = (1u << 29),
PROCESS_ROOT_IS_MAGISK = (1u << 30),
PROCESS_IS_SYS_UI = (1u << 31),
PROCESS_IS_FIRST_STARTED = (1u << 31),
PRIVATE_MASK = PROCESS_IS_SYS_UI
PRIVATE_MASK = PROCESS_IS_FIRST_STARTED
};
struct api_abi_base {

View File

@@ -1,145 +0,0 @@
#include <mntent.h>
#include <sys/mount.h>
#include "files.hpp"
#include "logging.h"
#include "misc.hpp"
#include "zygisk.hpp"
using namespace std::string_view_literals;
namespace {
constexpr auto MODULE_DIR = "/data/adb/modules";
constexpr auto KSU_OVERLAY_SOURCE = "KSU";
constexpr auto AP_OVERLAY_SOURCE = "APatch";
const std::vector<std::string> DEVICE_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"};
void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1) {
LOGD("Unmounted (%s)", mountpoint);
} else {
#ifndef NDEBUG
PLOGE("Unmount (%s)", mountpoint);
#endif
}
}
}
void revert_unmount_ksu() {
std::string ksu_loop;
std::vector<std::string> targets;
// Unmount ksu module dir last
targets.emplace_back(MODULE_DIR);
for (auto& info: parse_mount_info("self")) {
if (info.target == MODULE_DIR) {
ksu_loop = info.source;
continue;
}
// Unmount everything mounted to /data/adb
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
// Unmount everything mounted to /data/adb
if (info.root.starts_with("/adb/modules")) {
targets.emplace_back(info.target);
}
// Unmount ksu overlays
if (info.type == "overlay"
&& info.source == KSU_OVERLAY_SOURCE
&& std::find(DEVICE_PARTITIONS.begin(), DEVICE_PARTITIONS.end(), info.target) != DEVICE_PARTITIONS.end()) {
targets.emplace_back(info.target);
}
// Unmount temp dir
if (info.type == "tmpfs" && info.source == KSU_OVERLAY_SOURCE) {
targets.emplace_back(info.target);
}
}
for (auto& info: parse_mount_info("self")) {
// Unmount everything from ksu loop except ksu module dir
if (info.source == ksu_loop && info.target != MODULE_DIR) {
targets.emplace_back(info.target);
}
}
// Do unmount
for (auto& s: reversed(targets)) {
lazy_unmount(s.data());
}
}
void revert_unmount_magisk() {
std::vector<std::string> targets;
// Unmount dummy skeletons and MAGISKTMP
// since mirror nodes are always mounted under skeleton, we don't have to specifically unmount
for (auto& info: parse_mount_info("self")) {
if (info.source == "magisk" || info.source == "worker" || // magisktmp tmpfs
info.root.starts_with("/adb/modules")) { // bind mount from data partition
targets.push_back(info.target);
}
// Unmount everything mounted to /data/adb
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
}
for (auto& s: reversed(targets)) {
lazy_unmount(s.data());
}
}
void revert_unmount_apatch() {
std::string ap_loop;
std::vector<std::string> targets;
// Unmount ksu module dir last
targets.emplace_back(MODULE_DIR);
for (auto& info: parse_mount_info("self")) {
if (info.target == MODULE_DIR) {
ap_loop = info.source;
continue;
}
// Unmount everything mounted to /data/adb
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
// Unmount everything mounted to /data/adb
if (info.root.starts_with("/adb/modules")) {
targets.emplace_back(info.target);
}
// Unmount ksu overlays
if (info.type == "overlay"
&& info.source == AP_OVERLAY_SOURCE
&& std::find(DEVICE_PARTITIONS.begin(), DEVICE_PARTITIONS.end(), info.target) != DEVICE_PARTITIONS.end()) {
targets.emplace_back(info.target);
}
// Unmount temp dir
if (info.type == "tmpfs" && info.source == AP_OVERLAY_SOURCE) {
targets.emplace_back(info.target);
}
}
for (auto& info: parse_mount_info("self")) {
// Unmount everything from ksu loop except ksu module dir
if (info.source == ap_loop && info.target != MODULE_DIR) {
targets.emplace_back(info.target);
}
}
// Do unmount
for (auto& s: reversed(targets)) {
lazy_unmount(s.data());
}
}

View File

@@ -8,9 +8,3 @@ extern size_t block_size;
void hook_functions();
void clean_trace(const char* path, size_t load = 1, size_t unload = 0, bool spoof_maps = false);
void revert_unmount_ksu();
void revert_unmount_magisk();
void revert_unmount_apatch();

View File

@@ -1,21 +1,15 @@
allow zygote tmpfs file *
allow zygote appdomain_tmpfs file *
type zygisk_file file_type
typeattribute zygisk_file mlstrustedobject
allow zygote zygisk_file sock_file {read write}
type magisk_file file_type
typeattribute magisk_file mlstrustedobject
allow * magisk_file file *
allow * magisk_file dir *
allow * magisk_file fifo_file *
allow * magisk_file chr_file *
allow * magisk_file lnk_file *
allow * magisk_file sock_file *
allow system_server system_server process execmem
allow zygote zygote process execmem
allow zygote magisk lnk_file read
allow zygote unlabeled file {read open}
allow zygote zygote capability sys_chroot
allow zygote su dir search
allow zygote su {lnk_file file} read
allow zygote adb_data_file dir search
allow zygote mnt_vendor_file dir search
allow zygote system_file dir mounton
allow zygote labeledfs filesystem mount
allow zygote fs_type filesystem unmount
allow zygote zygote process execmem
allow system_server system_server process execmem
allow zygote tmpfs file *
allow zygote appdomain_tmpfs file *

View File

@@ -42,7 +42,7 @@ val CFlagsRelease = arrayOf(
)
val CFlagsDebug = arrayOf(
"-g", "-O0"
"-g", "-O0", "-DDEBUG"
)
val Files = arrayOf(

View File

@@ -7,12 +7,6 @@
#define true 1
#define false 0
#if DEBUG == false
#define MAX_LOG_LEVEL ANDROID_LOG_VERBOSE
#else
#define MAX_LOG_LEVEL ANDROID_LOG_INFO
#endif
#if (defined(__LP64__) || defined(_LP64))
#define lp_select(a, b) b
#else
@@ -26,25 +20,24 @@
enum DaemonSocketAction {
PingHeartbeat = 0,
RequestLogcatFd = 1,
GetProcessFlags = 2,
GetInfo = 3,
ReadModules = 4,
RequestCompanionSocket = 5,
GetModuleDir = 6,
ZygoteRestart = 7,
SystemServerStarted = 8
GetProcessFlags = 1,
GetInfo = 2,
ReadModules = 3,
RequestCompanionSocket = 4,
GetModuleDir = 5,
ZygoteRestart = 6,
SystemServerStarted = 7,
GetCleanNamespace = 8
};
enum ProcessFlags: uint32_t {
PROCESS_GRANTED_ROOT = (1u << 0),
PROCESS_ON_DENYLIST = (1u << 1),
PROCESS_IS_MANAGER = (1u << 28),
PROCESS_ROOT_IS_APATCH = (1u << 27),
PROCESS_IS_MANAGER = (1u << 27),
PROCESS_ROOT_IS_APATCH = (1u << 28),
PROCESS_ROOT_IS_KSU = (1u << 29),
PROCESS_ROOT_IS_MAGISK = (1u << 30),
PROCESS_IS_SYS_UI = (1u << 31),
PROCESS_IS_SYSUI = (1u << 31)
PROCESS_IS_FIRST_STARTED = (1u << 31)
};
enum RootImplState {
@@ -54,4 +47,10 @@ enum RootImplState {
Abnormal
};
enum MountNamespaceState {
Clean,
Rooted,
Module
};
#endif /* CONSTANTS_H */

View File

@@ -1,6 +1,9 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../utils.h"
#include "kernelsu.h"

View File

@@ -3,11 +3,13 @@
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <errno.h>
#include <sys/sysmacros.h>
#include <sys/mount.h>
#include <unistd.h>
#include <linux/limits.h>
@@ -18,6 +20,11 @@
#include "utils.h"
#include "root_impl/common.h"
#include "root_impl/magisk.h"
int clean_namespace_fd = 0;
int rooted_namespace_fd = 0;
int module_namespace_fd = 0;
bool switch_mount_namespace(pid_t pid) {
char path[PATH_MAX];
@@ -184,7 +191,7 @@ int unix_listener_from_path(char *restrict path) {
return -1;
}
if (chcon(path, "u:object_r:magisk_file:s0") == -1) {
if (chcon(path, "u:object_r:zygisk_file:s0") == -1) {
LOGE("chcon: %s\n", strerror(errno));
return -1;
@@ -450,3 +457,381 @@ void stringify_root_impl_name(struct root_impl impl, char *restrict output) {
}
}
}
struct mountinfo {
unsigned int id;
unsigned int parent;
dev_t device;
const char *root;
const char *target;
const char *vfs_option;
struct {
unsigned int shared;
unsigned int master;
unsigned int propagate_from;
} optional;
const char *type;
const char *source;
const char *fs_option;
};
struct mountinfos {
struct mountinfo *mounts;
size_t length;
};
char *strndup(const char *restrict str, size_t length) {
char *restrict copy = malloc(length + 1);
if (copy == NULL) return NULL;
memcpy(copy, str, length);
copy[length] = '\0';
return copy;
}
void free_mounts(struct mountinfos *restrict mounts) {
for (size_t i = 0; i < mounts->length; i++) {
free((void *)mounts->mounts[i].root);
free((void *)mounts->mounts[i].target);
free((void *)mounts->mounts[i].vfs_option);
free((void *)mounts->mounts[i].type);
free((void *)mounts->mounts[i].source);
free((void *)mounts->mounts[i].fs_option);
}
free((void *)mounts->mounts);
}
bool parse_mountinfo(const char *restrict pid, struct mountinfos *restrict mounts) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/%s/mountinfo", pid);
FILE *mountinfo = fopen(path, "r");
if (mountinfo == NULL) {
LOGE("fopen: %s\n", strerror(errno));
return false;
}
char line[PATH_MAX];
size_t i = 0;
mounts->mounts = NULL;
mounts->length = 0;
while (fgets(line, sizeof(line), mountinfo) != NULL) {
int root_start = 0, root_end = 0;
int target_start = 0, target_end = 0;
int vfs_option_start = 0, vfs_option_end = 0;
int type_start = 0, type_end = 0;
int source_start = 0, source_end = 0;
int fs_option_start = 0, fs_option_end = 0;
int optional_start = 0, optional_end = 0;
unsigned int id, parent, maj, min;
sscanf(line,
"%u " // (1) id
"%u " // (2) parent
"%u:%u " // (3) maj:min
"%n%*s%n " // (4) mountroot
"%n%*s%n " // (5) target
"%n%*s%n" // (6) vfs options (fs-independent)
"%n%*[^-]%n - " // (7) optional fields
"%n%*s%n " // (8) FS type
"%n%*s%n " // (9) source
"%n%*s%n", // (10) fs options (fs specific)
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
&target_end, &vfs_option_start, &vfs_option_end,
&optional_start, &optional_end, &type_start, &type_end,
&source_start, &source_end, &fs_option_start, &fs_option_end);
mounts->mounts = (struct mountinfo *)realloc(mounts->mounts, (i + 1) * sizeof(struct mountinfo));
if (!mounts->mounts) {
LOGE("Failed to allocate memory for mounts->mounts");
fclose(mountinfo);
free_mounts(mounts);
return false;
}
unsigned int shared = 0;
unsigned int master = 0;
unsigned int propagate_from = 0;
if (strstr(line + optional_start, "shared:")) {
shared = (unsigned int)atoi(strstr(line + optional_start, "shared:") + 7);
}
if (strstr(line + optional_start, "master:")) {
master = (unsigned int)atoi(strstr(line + optional_start, "master:") + 7);
}
if (strstr(line + optional_start, "propagate_from:")) {
propagate_from = (unsigned int)atoi(strstr(line + optional_start, "propagate_from:") + 15);
}
mounts->mounts[i].id = id;
mounts->mounts[i].parent = parent;
mounts->mounts[i].device = (dev_t)(makedev(maj, min));
mounts->mounts[i].root = strndup(line + root_start, (size_t)(root_end - root_start));
mounts->mounts[i].target = strndup(line + target_start, (size_t)(target_end - target_start));
mounts->mounts[i].vfs_option = strndup(line + vfs_option_start, (size_t)(vfs_option_end - vfs_option_start));
mounts->mounts[i].optional.shared = shared;
mounts->mounts[i].optional.master = master;
mounts->mounts[i].optional.propagate_from = propagate_from;
mounts->mounts[i].type = strndup(line + type_start, (size_t)(type_end - type_start));
mounts->mounts[i].source = strndup(line + source_start, (size_t)(source_end - source_start));
mounts->mounts[i].fs_option = strndup(line + fs_option_start, (size_t)(fs_option_end - fs_option_start));
i++;
}
fclose(mountinfo);
mounts->length = i;
return true;
}
enum mns_umount_state {
Complete,
NotComplete,
Error
};
enum mns_umount_state unmount_root(bool modules_only, struct root_impl impl) {
/* INFO: We are already in the target pid mount namespace, so actually,
when we use self here, we meant its pid.
*/
struct mountinfos mounts;
if (!parse_mountinfo("self", &mounts)) {
LOGE("Failed to parse mountinfo\n");
return Error;
}
/* INFO: Implementations like Magisk Kitsune will mount MagiskSU when boot is completed,
so if we cache the clean mount done before the boot is completed, it will get
it mounted later and hence it will leak mounts. To avoid that we will detect
if implementation is Kitsune, and if so, see if /system/bin... is mounted,
if not, it won't cache this namespace. */
bool magiskSU_umounted = false;
switch (impl.impl) {
case None: { break; }
case Multiple: { break; }
case KernelSU:
case APatch: {
char source_name[LONGEST_ROOT_IMPL_NAME];
if (impl.impl == KernelSU) strcpy(source_name, "KSU");
else strcpy(source_name, "APatch");
const char **targets_to_unmount = NULL;
size_t num_targets = 0;
for (size_t i = 0; i < mounts.length; i++) {
struct mountinfo mount = mounts.mounts[i];
bool should_unmount = false;
if (modules_only) {
if (strncmp(mount.target, "/debug_ramdisk", strlen("/debug_ramdisk")) == 0)
should_unmount = true;
} else {
if (strcmp(mount.source, source_name) == 0) should_unmount = true;
if (strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0) should_unmount = true;
if (strncmp(mount.target, "/data/adb/modules", strlen("/data/adb/modules")) == 0) should_unmount = true;
}
if (!should_unmount) continue;
num_targets++;
targets_to_unmount = realloc(targets_to_unmount, num_targets * sizeof(char*));
if (targets_to_unmount == NULL) {
LOGE("[%s] Failed to allocate memory for targets_to_unmount\n", source_name);
free(targets_to_unmount);
free_mounts(&mounts);
return Error;
}
targets_to_unmount[num_targets - 1] = mount.target;
}
for (size_t i = num_targets; i > 0; i--) {
const char *target = targets_to_unmount[i - 1];
if (umount2(target, MNT_DETACH) == -1) {
LOGE("[%s] Failed to unmount %s: %s\n", source_name, target, strerror(errno));
} else {
LOGI("[%s] Unmounted %s\n", source_name, target);
}
}
free(targets_to_unmount);
break;
}
case Magisk: {
LOGI("[Magisk] Unmounting root %s modules\n", modules_only ? "only" : "with");
const char **targets_to_unmount = NULL;
size_t num_targets = 0;
for (size_t i = 0; i < mounts.length; i++) {
struct mountinfo mount = mounts.mounts[i];
bool should_unmount = false;
if (
(
modules_only &&
(
strcmp(mount.source, "magisk") == 0 ||
strncmp(mount.target, "/debug_ramdisk", strlen("/debug_ramdisk")) == 0 ||
strncmp(mount.target, "/system/bin", strlen("/system/bin")) == 0
)
) ||
(
!modules_only &&
(
strcmp(mount.source, "magisk") == 0 ||
strncmp(mount.target, "/debug_ramdisk", strlen("/debug_ramdisk")) == 0 ||
strncmp(mount.target, "/data/adb/modules", strlen("/data/adb/modules")) == 0 ||
strncmp(mount.root, "/adb/modules", strlen("/adb/modules")) == 0 ||
strncmp(mount.target, "/system/bin", strlen("/system/bin")) == 0
)
)
) {
should_unmount = true;
}
if (!should_unmount) continue;
num_targets++;
targets_to_unmount = realloc(targets_to_unmount, num_targets * sizeof(char*));
if (targets_to_unmount == NULL) {
LOGE("[Magisk] Failed to allocate memory for targets_to_unmount\n");
free(targets_to_unmount);
free_mounts(&mounts);
return Error;
}
targets_to_unmount[num_targets - 1] = mount.target;
if (impl.impl == Magisk && strncmp(mount.target, "/system/bin", strlen("/system/bin")) == 0)
magiskSU_umounted = true;
}
for (size_t i = num_targets; i > 0; i--) {
const char *target = targets_to_unmount[i - 1];
if (umount2(target, MNT_DETACH) == -1) {
LOGE("[Magisk] Failed to unmount %s: %s\n", target, strerror(errno));
} else {
LOGI("[Magisk] Unmounted %s\n", target);
}
}
free(targets_to_unmount);
break;
}
}
free_mounts(&mounts);
return (impl.impl == Magisk && !magiskSU_umounted) ? NotComplete : Complete;
}
int save_mns_fd(int pid, enum MountNamespaceState mns_state, struct root_impl impl) {
if (mns_state == Clean && clean_namespace_fd != 0) return clean_namespace_fd;
if (mns_state == Rooted && rooted_namespace_fd != 0) return rooted_namespace_fd;
if (mns_state == Module && module_namespace_fd != 0) return module_namespace_fd;
int sockets[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
LOGE("socketpair: %s\n", strerror(errno));
return -1;
}
int reader = sockets[0];
int writer = sockets[1];
pid_t fork_pid = fork();
if (fork_pid == 0) {
switch_mount_namespace(pid);
enum mns_umount_state umount_state = Complete;
if (mns_state != Rooted) {
unshare(CLONE_NEWNS);
umount_state = unmount_root(mns_state == Module, impl);
if (umount_state == Error) {
write_uint8_t(writer, (uint8_t)umount_state);
_exit(1);
}
}
uint32_t mypid = 0;
while (mypid != (uint32_t)getpid()) {
write_uint8_t(writer, (uint8_t)umount_state);
usleep(50);
read_uint32_t(reader, &mypid);
}
_exit(0);
} else if (fork_pid > 0) {
enum mns_umount_state umount_state = (enum mns_umount_state)0;
read_uint8_t(reader, (uint8_t *)&umount_state);
if (umount_state == Error) {
LOGE("Failed to unmount root\n");
return -1;
}
char ns_path[PATH_MAX];
snprintf(ns_path, PATH_MAX, "/proc/%d/ns/mnt", fork_pid);
int ns_fd = open(ns_path, O_RDONLY);
if (ns_fd == -1) {
LOGE("open: %s\n", strerror(errno));
return -1;
}
write_uint32_t(writer, (uint32_t)fork_pid);
if (close(reader) == -1) {
LOGE("Failed to close reader: %s\n", strerror(errno));
return -1;
}
if (close(writer) == -1) {
LOGE("Failed to close writer: %s\n", strerror(errno));
return -1;
}
if (waitpid(fork_pid, NULL, 0) == -1) {
LOGE("waitpid: %s\n", strerror(errno));
return -1;
}
if (mns_state == Rooted) return (rooted_namespace_fd = ns_fd);
else if (mns_state == Clean && umount_state == Complete) return (clean_namespace_fd = ns_fd);
else if (mns_state == Module && umount_state == Complete) return (module_namespace_fd = ns_fd);
else return ns_fd;
} else {
LOGE("fork: %s\n", strerror(errno));
return -1;
}
return -1;
}

View File

@@ -107,4 +107,6 @@ int non_blocking_execv(const char *restrict file, char *const argv[]);
void stringify_root_impl_name(struct root_impl impl, char *restrict output);
int save_mns_fd(int pid, enum MountNamespaceState mns_state, struct root_impl impl);
#endif /* UTILS_H */

View File

@@ -382,6 +382,7 @@ void zygiskd_start(char *restrict argv[]) {
return;
}
bool first_process = true;
while (1) {
int client_fd = accept(socket_fd, NULL, NULL);
if (client_fd == -1) {
@@ -437,51 +438,26 @@ void zygiskd_start(char *restrict argv[]) {
break;
}
/* TODO: Move to another thread and save client fds to an epoll list
so that we can, in a single-thread, deal with multiple logcats */
case RequestLogcatFd: {
uint8_t level = 0;
ssize_t ret = read_uint8_t(client_fd, &level);
ASSURE_SIZE_READ_BREAK("RequestLogcatFd", "level", ret, sizeof(level));
char tag[128 + 1];
ret = read_string(client_fd, tag, sizeof(tag));
if (ret == -1) {
LOGE("Failed reading logcat tag.\n");
close(client_fd);
break;
}
char message[1024 + 1];
ret = read_string(client_fd, message, sizeof(message));
if (ret == -1) {
LOGE("Failed reading logcat message.\n");
close(client_fd);
break;
}
__android_log_print(level, tag, "%s", message);
break;
}
case GetProcessFlags: {
uint32_t uid = 0;
ssize_t ret = read_uint32_t(client_fd, &uid);
ASSURE_SIZE_READ_BREAK("GetProcessFlags", "uid", ret, sizeof(uid));
uint32_t flags = 0;
if (uid_is_manager(uid)) {
flags |= PROCESS_IS_MANAGER;
if (first_process) {
flags |= PROCESS_IS_FIRST_STARTED;
first_process = false;
} else {
if (uid_granted_root(uid)) {
flags |= PROCESS_GRANTED_ROOT;
}
if (uid_should_umount(uid)) {
flags |= PROCESS_ON_DENYLIST;
if (uid_is_manager(uid)) {
flags |= PROCESS_IS_MANAGER;
} else {
if (uid_granted_root(uid)) {
flags |= PROCESS_GRANTED_ROOT;
}
if (uid_should_umount(uid)) {
flags |= PROCESS_ON_DENYLIST;
}
}
}
@@ -671,11 +647,35 @@ void zygiskd_start(char *restrict argv[]) {
break;
}
break;
}
case GetCleanNamespace: {
pid_t pid = 0;
ssize_t ret = read_uint32_t(client_fd, (uint32_t *)&pid);
ASSURE_SIZE_READ_BREAK("GetCleanNamespace", "pid", ret, sizeof(pid));
uint8_t mns_state = 0;
ret = read_uint8_t(client_fd, &mns_state);
ASSURE_SIZE_READ_BREAK("GetCleanNamespace", "mns_state", ret, sizeof(mns_state));
uint32_t our_pid = (uint32_t)getpid();
ret = write_uint32_t(client_fd, (uint32_t)our_pid);
ASSURE_SIZE_WRITE_BREAK("GetCleanNamespace", "our_pid", ret, sizeof(our_pid));
if ((enum MountNamespaceState)mns_state == Clean) {
save_mns_fd(pid, Rooted, impl);
save_mns_fd(pid, Module, impl);
}
uint32_t clean_namespace_fd = (uint32_t)save_mns_fd(pid, (enum MountNamespaceState)mns_state, impl);
ret = write_uint32_t(client_fd, clean_namespace_fd);
ASSURE_SIZE_WRITE_BREAK("GetCleanNamespace", "clean_namespace_fd", ret, sizeof(clean_namespace_fd));
break;
}
}
if (action != RequestCompanionSocket && action != RequestLogcatFd) close(client_fd);
if (action != RequestCompanionSocket) close(client_fd);
continue;
}