diff --git a/module/jni/Android.mk b/module/jni/Android.mk index 746588e..db296bc 100644 --- a/module/jni/Android.mk +++ b/module/jni/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := zygisk -LOCAL_SRC_FILES := main.cpp +LOCAL_SRC_FILES := mount_parser.cpp unmount.cpp main.cpp LOCAL_STATIC_LIBRARIES := libcxx LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) diff --git a/module/jni/logging.hpp b/module/jni/logging.hpp new file mode 100644 index 0000000..9f6cd0f --- /dev/null +++ b/module/jni/logging.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +#ifndef NDEBUG +static constexpr auto TAG = "ZygiskAssistant/JNI"; +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) +#else +#define LOGD(...) +#define LOGI(...) +#define LOGE(...) +#endif diff --git a/module/jni/main.cpp b/module/jni/main.cpp index 7129831..f463268 100644 --- a/module/jni/main.cpp +++ b/module/jni/main.cpp @@ -1,14 +1,15 @@ -#include #include -#include -#include +#include #include "zygisk.hpp" +#include "logging.hpp" using zygisk::Api; using zygisk::AppSpecializeArgs; using zygisk::ServerSpecializeArgs; +void do_unmount(); + class ZygiskModule : public zygisk::ModuleBase { public: @@ -25,22 +26,30 @@ public: uint32_t flags = api->getFlags(); bool isRoot = (flags & zygisk::StateFlag::PROCESS_GRANTED_ROOT) != 0; bool isOnDenylist = (flags & zygisk::StateFlag::PROCESS_ON_DENYLIST) != 0; - if (!isRoot && isOnDenylist && args->uid > 1000) { - api->setOption(zygisk::Option::FORCE_DENYLIST_UNMOUNT); - } - } + LOGD("Unmounting in preAppSpecialize for pid=%d uid=%d", getpid(), args->uid); - void preServerSpecialize(ServerSpecializeArgs *args) - { - api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); + /* + * preAppSpecialize is before ensureInAppMountNamespace. + * postAppSpecialize is after seccomp setup. + * So we unshare here to create a pseudo app mount namespace + */ + if (unshare(CLONE_NEWNS) == 0) + { + LOGD("unshare(CLONE_NEWNS) returned 0"); + do_unmount(); + } + else + { + LOGE("unshare(CLONE_NEWNS) returned -1: %d (%s)", errno, strerror(errno)); + } + } } private: Api *api; JNIEnv *env; - }; REGISTER_ZYGISK_MODULE(ZygiskModule) diff --git a/module/jni/mount_parser.cpp b/module/jni/mount_parser.cpp new file mode 100644 index 0000000..0d39728 --- /dev/null +++ b/module/jni/mount_parser.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#include + +#include "mount_parser.hpp" +#include "logging.hpp" + +mount_entry_t::mount_entry_t(::mntent *entry) + : fsname(entry->mnt_fsname), dir(entry->mnt_dir), type(entry->mnt_type), freq(entry->mnt_freq), passno(entry->mnt_passno) +{ + parseMountOptions(entry->mnt_opts); +} + +const std::string &mount_entry_t::getFsName() const +{ + return fsname; +} + +const std::string &mount_entry_t::getMountPoint() const +{ + return dir; +} + +const std::string &mount_entry_t::getType() const +{ + return type; +} + +const std::unordered_map &mount_entry_t::getOptions() const +{ + return opts_map; +} + +int mount_entry_t::getDumpFrequency() const +{ + return freq; +} + +int mount_entry_t::getPassNumber() const +{ + return passno; +} + +void mount_entry_t::parseMountOptions(const std::string &input) +{ + std::istringstream iss(input); + std::string token; + while (std::getline(iss, token, ',')) + { + std::istringstream tokenStream(token); + std::string key, value; + + if (std::getline(tokenStream, key, '=')) + { + std::getline(tokenStream, value); // Put what's left in the stream to value, could be empty + opts_map[key] = value; + } + } +} + +std::vector parseMountsFromPath(const char *path) +{ + std::vector result; + + FILE *file = setmntent(path, "r"); + if (file == NULL) + { + LOGE("setmntent(\"%s\", \"r\") returned NULL: %d (%s)", path, errno, strerror(errno)); + return result; + } + + struct mntent *entry; + while ((entry = getmntent(file)) != NULL) + { + result.emplace_back(mount_entry_t(entry)); + } + + endmntent(file); + return result; +} diff --git a/module/jni/mount_parser.hpp b/module/jni/mount_parser.hpp new file mode 100644 index 0000000..f5e26fb --- /dev/null +++ b/module/jni/mount_parser.hpp @@ -0,0 +1,29 @@ +#include +#include +#include + +#include + +class mount_entry_t +{ +public: + mount_entry_t(::mntent *entry); + const std::string &getFsName() const; + const std::string &getMountPoint() const; + const std::string &getType() const; + const std::unordered_map &getOptions() const; + int getDumpFrequency() const; + int getPassNumber() const; + +private: + void parseMountOptions(const std::string &input); + + std::string fsname; + std::string dir; + std::string type; + std::unordered_map opts_map; + int freq; + int passno; +}; + +std::vector parseMountsFromPath(const char *path); diff --git a/module/jni/unmount.cpp b/module/jni/unmount.cpp new file mode 100644 index 0000000..5374def --- /dev/null +++ b/module/jni/unmount.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include + +#include "zygisk.hpp" +#include "logging.hpp" +#include "mount_parser.hpp" + +constexpr std::array fsname_list = {"KSU", "APatch", "magisk", "worker"}; + +static bool shouldUnmount(const mount_entry_t &info) +{ + const auto &mountPoint = info.getMountPoint(); + const auto &type = info.getType(); + const auto &options = info.getOptions(); + + // Unmount everything mounted to /data/adb + if (mountPoint.rfind("/data/adb", 0) == 0) + return true; + + // Unmount all module overlayfs and tmpfs + bool doesFsnameMatch = std::find(fsname_list.begin(), fsname_list.end(), info.getFsName()) != fsname_list.end(); + if ((type == "overlay" || type == "tmpfs") && doesFsnameMatch) + return true; + + // Unmount all overlayfs with lowerdir/upperdir/workdir starting with /data/adb + if (type == "overlay") + { + const auto &lowerdir = options.find("lowerdir"); + const auto &upperdir = options.find("upperdir"); + const auto &workdir = options.find("workdir"); + + if (lowerdir != options.end() && lowerdir->second.rfind("/data/adb", 0) == 0) + return true; + + if (upperdir != options.end() && upperdir->second.rfind("/data/adb", 0) == 0) + return true; + + if (workdir != options.end() && workdir->second.rfind("/data/adb", 0) == 0) + return true; + } + return false; +} + +void do_unmount() +{ + std::vector mountPoints; + for (auto &info : parseMountsFromPath("/proc/self/mounts")) + { + if (shouldUnmount(info)) + { + mountPoints.push_back(info.getMountPoint()); + } + } + + // Sort by string lengths, descending + std::sort(mountPoints.begin(), mountPoints.end(), [](const auto &lhs, const auto &rhs) + { return lhs.size() > rhs.size(); }); + + for (const auto &mountPoint : mountPoints) + { + if (umount2(mountPoint.c_str(), MNT_DETACH) == 0) + { + LOGD("umount2(\"%s\", MNT_DETACH) returned 0", mountPoint.c_str()); + } + else + { + LOGE("umount2(\"%s\", MNT_DETACH) returned -1: %d (%s)", mountPoint.c_str(), errno, strerror(errno)); + } + } +} diff --git a/module/template/module.prop b/module/template/module.prop index 82cab09..d4ada1a 100644 --- a/module/template/module.prop +++ b/module/template/module.prop @@ -2,5 +2,5 @@ id=${moduleId} name=${moduleName} version=${versionName} versionCode=${versionCode} -author=snake-4 & yervant7 -description=DLCLOSE_MODULE_LIBRARY and FORCE_DENYLIST_UNMOUNT for non-root processes. +author=snake-4 +description=Zygisk module to hide mounts.