diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 530795fe..61afb401 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,20 +34,6 @@ jobs: with: gradle-home-cache-cleanup: true - - name: Setup rust-cache - uses: Swatinem/rust-cache@v2 - with: - workspaces: zygiskd/src -> ../build/intermediates/rust - cache-targets: false - - - name: Setup Rust - run: | - rustup override set nightly - rustup target add aarch64-linux-android - rustup target add x86_64-linux-android - rustup target add i686-linux-android - rustup target add armv7-linux-androideabi - - name: Set up ccache uses: hendrikmuhs/ccache-action@v1.2 with: @@ -74,12 +60,6 @@ jobs: debugName=`ls module/build/outputs/release/ReZygisk-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT unzip module/build/outputs/release/ReZygisk-v*-release.zip -d zksu-release unzip module/build/outputs/release/ReZygisk-v*-debug.zip -d zksu-debug - releaseSymbolName="SYMBOLS-$releaseName.zip" - debugSymbolName="SYMBOLS-$debugName.zip" - echo "releaseSymbolName=$releaseSymbolName" >> $GITHUB_OUTPUT - echo "debugSymbolName=$debugSymbolName" >> $GITHUB_OUTPUT - zip -r $releaseSymbolName zygiskd/build/symbols/release - zip -r $debugSymbolName zygiskd/build/symbols/debug - name: Upload release uses: actions/upload-artifact@v4 @@ -92,16 +72,3 @@ jobs: with: name: ${{ steps.prepareArtifact.outputs.debugName }} path: "./zksu-debug/*" - - - name: Upload release symbols - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.prepareArtifact.outputs.releaseName }}-symbols - path: "zygiskd/build/symbols/release" - - - name: Upload debug symbols - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.prepareArtifact.outputs.debugName }}-symbols - path: "zygiskd/build/symbols/debug" - diff --git a/README.md b/README.md index 00d534c8..e28f4d86 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ReZygisk +[中文](https://github.com/PerformanC/ReZygisk/blob/main/README_CN.md) + ReZygisk is a fork of Zygisk Next, a standalone implementation of Zygisk, providing Zygisk API support for KernelSU, Magisk (besides built-in), and APatch (Work In Progress). It aims to modernize and re-write the codebase to C (from C++ and Rust), allowing a more efficient and faster implementation of the Zygisk API with a more permissive license. @@ -18,7 +20,6 @@ The Zygisk Next developers are famous and trusted in the Android community, howe ## Advantages - FOSS (Forever) -- Better Standalone Hiding ## Dependencies @@ -76,4 +77,4 @@ It is mandatory to follow PerformanC's [Contribution Guidelines](https://github. ## License -ReZygisk is licensed majority under GPL, by Dr-TSNG, but also by BSD 2-Clause License for re-written code. You can read more about it on [Open Source Initiative](https://opensource.org/licenses/BSD-2-Clause). +ReZygisk is licensed majoritaly under GPL, by Dr-TSNG, but also AGPL 3.0, by The PerformanC Organization, for re-written code. You can read more about it on [Open Source Initiative](https://opensource.org/licenses/AGPL-3.0). diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 00000000..7d03c7f5 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,87 @@ +# ReZygisk + +[English](https://github.com/PerformanC/ReZygisk/blob/main/README.md) + +ReZygisk 是 Zygisk 的另一个独立实现,从 Zygisk Next 分叉而来,为 KernelSU、Magisk 和 APatch 提供 Zygisk API 支持(目前处于测试阶段)。 + +项目致力于使用 C 语言重写原本的 C++ 和 Rust 代码,从而更加现代、高效地实现 Zygisk API,并使用更宽松的开源许可证。 + +> [!NOTE] +> 此模块还在测试阶段,请仅安装正式版本的压缩包。 +> +> 您可以从 [Actions](https://github.com/PerformanC/ReZygisk/actions) 页面下载自动构建包,但要注意自负风险。使用不稳定的版本时,设备可能会陷入启动循环(Bootloop)。 + +## 为什么要ReZygisk? + +最新版本的 Zygisk Next 并不开源,仅其核心开发者有权查阅全部源代码。这不仅阻止了其他开发者贡献代码,还阻止了他们对项目代码进行审计。Zygisk Next 是一个以超级用户(root)权限运行的模块,可以访问整个系统,闭源后存在重大安全隐患。 + +Zygisk Next 的开发者们在Android社区享有盛誉,备受信任。但这并不意味着他们的项目就一定没有任何恶意代码和漏洞。我们(PerformanC)理解他们出于某些原因不愿保持开源,但我们坚信,开源是更好的选择。 + +## 优点 + +- 永远保持开源 + +## 依赖 + +| 工具 | 简介 | +|---------------|------------------------------------| +| `rustc` | Rust 编译器 | +| `Android NDK` | Android 本地开发工具包 | + +### Rust 依赖 + +| 依赖 | 简介 | +|------------------|-------------------------------------------------------| +| `android_logger` | Android 日志记录工具 | +| `anyhow` | 错误处理库 | +| `bitflags` | 用于生成位操作结构的宏 | +| `const_format` | 编译时字符串格式化 | +| `futures` | 异步编程的抽象实现 | +| `konst` | 编译时常量比较 | +| `lazy_static` | 用于声明静态延迟比较变量的宏 | +| `libc` | 对各平台系统C语言库的原始 FFI 绑定 | +| `log` | 日志外观框架 | +| `memfd` | 内存文件描述符实用程序 | +| `num_enum` | 在数字类型和枚举类型之间进行转换的衍生宏 | +| `passfd` | 通过Unix套接字传递文件描述符 | +| `proc-maps` | 解析和分析进程内存映射 | + +### C++ 依赖 + +| 依赖 | 简介 | +|---------|-----------------------------| +| `lsplt` | Android 程序链接表钩子 | + +## 用法 + +目前正在编写中 (敬请期待) + +## 安装 + +目前还没有发布正式版本 (敬请期待) + +## 翻译 + +您可以向 [add/webui](https://github.com/PerformanC/ReZygisk/tree/add/webui) 分支贡献翻译。 + +请不要忘记在 [TRANSLATOR.md](https://github.com/PerformanC/ReZygisk/blob/add/webui/TRANSLATOR.md) 中添加您的 GitHub 账号信息,以便人们看到您的贡献。 + +## 支持 + +如果您有任何关于 ReZygisk 或者其他 PerformanC 项目的问题,可以随时加入以下群组: + +- Discord 服务器: [PerformanC](https://discord.gg/uPveNfTuCJ) +- ReZygisk Telegram 群组: [@rezygiskchat](https://t.me/rezygiskchat) +- PerformanC Telegram 群组: [@performancorg](https://t.me/performancorg) + +## 贡献 + +要对 ReZygisk 做出贡献,请遵守 PerformanC 的 [贡献指南](https://github.com/PerformanC/contributing) 。 + +请遵循其安全政策、行为准则和语法标准。 + +## 开源许可证 + +ReZygisk 项目中,旧的 Zygisk Next 部分采用 GPL 许可证,但由 PerformanC 组织重写的代码则采用 AGPL 3.0 许可证。 + +您可以在 [Open Source Initiative](https://opensource.org/licenses/AGPL-3.0) 上阅读更多相关信息。 diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index aa352b4e..144ffba6 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -33,7 +33,7 @@ val defaultCFlags = arrayOf( ) val releaseFlags = arrayOf( - "-Oz", "-flto", + "-Ofast", "-flto=thin", "-Wno-unused", "-Wno-unused-parameter", "-fvisibility=hidden", "-fvisibility-inlines-hidden", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp index 1b73a381..34a188cb 100644 --- a/loader/src/common/daemon.cpp +++ b/loader/src/common/daemon.cpp @@ -1,225 +1,263 @@ #include #include #include +#include +#include +#include +#include #include "daemon.h" #include "dl.h" #include "socket_utils.h" namespace zygiskd { - static std::string TMP_PATH; - void Init(const char *path) { - TMP_PATH = path; + static std::string TMP_PATH; + void Init(const char *path) { + TMP_PATH = path; + } + + std::string GetTmpPath() { + return TMP_PATH; + } + + int Connect(uint8_t retry) { + retry++; + + int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = { 0 } + }; + + auto socket_path = TMP_PATH + kCPSocketName; + strcpy(addr.sun_path, socket_path.c_str()); + socklen_t socklen = sizeof(addr); + + while (--retry) { + int r = connect(fd, (struct sockaddr *)&addr, socklen); + if (r == 0) return fd; + if (retry) { + PLOGE("Retrying to connect to zygiskd, sleep 1s"); + + sleep(1); + } } - std::string GetTmpPath() { - return TMP_PATH; - } + close(fd); - int Connect(uint8_t retry) { - int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); - struct sockaddr_un addr = { - .sun_family = AF_UNIX, - .sun_path = { 0 }, - }; - - auto socket_path = TMP_PATH + kCPSocketName; - strcpy(addr.sun_path, socket_path.c_str()); - socklen_t socklen = sizeof(addr); - - while (retry--) { - int r = connect(fd, reinterpret_cast(&addr), socklen); - if (r == 0) return fd; - if (retry) { - PLOGE("Retrying to connect to zygiskd, sleep 1s"); - sleep(1); - } - } + return -1; + } - close(fd); - return -1; + bool PingHeartbeat() { + int fd = Connect(5); + if (fd == -1) { + PLOGE("Connect to zygiskd"); + + return false; } - bool PingHeartbeat() { - UniqueFd fd = Connect(5); - if (fd == -1) { - PLOGE("Connect to zygiskd"); - return false; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat); - return true; + socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat); + + close(fd); + + return true; + } + + int RequestLogcatFd() { + int fd = Connect(1); + if (fd == -1) { + PLOGE("RequestLogcatFd"); + + return -1; } - int RequestLogcatFd() { - int fd = Connect(1); - if (fd == -1) { - PLOGE("RequestLogcatFd"); - return -1; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd); - return fd; + socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd); + + return fd; + } + + uint32_t GetProcessFlags(uid_t uid) { + int fd = Connect(1); + if (fd == -1) { + PLOGE("GetProcessFlags"); + + return 0; } - uint32_t GetProcessFlags(uid_t uid) { - UniqueFd fd = Connect(1); - if (fd == -1) { - PLOGE("GetProcessFlags"); - return 0; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags); - socket_utils::write_u32(fd, uid); + socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags); + socket_utils::write_u32(fd, uid); - return socket_utils::read_u32(fd); + uint32_t res = socket_utils::read_u32(fd); + + close(fd); + + return res; + } + + std::vector ReadModules() { + std::vector modules; + int fd = Connect(1); + if (fd == -1) { + PLOGE("ReadModules"); + + return modules; } - std::vector ReadModules() { - std::vector modules; - UniqueFd fd = Connect(1); - if (fd == -1) { - PLOGE("ReadModules"); - return modules; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules); - size_t len = socket_utils::read_usize(fd); - for (size_t i = 0; i < len; i++) { - std::string name = socket_utils::read_string(fd); - int module_fd = socket_utils::recv_fd(fd); - modules.emplace_back(name, module_fd); - } - return modules; + socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules); + size_t len = socket_utils::read_usize(fd); + for (size_t i = 0; i < len; i++) { + std::string name = socket_utils::read_string(fd); + int module_fd = socket_utils::recv_fd(fd); + modules.emplace_back(name, module_fd); } - int ConnectCompanion(size_t index) { - int fd = Connect(1); - if (fd == -1) { - PLOGE("ConnectCompanion"); - return -1; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket); - socket_utils::write_usize(fd, index); - if (socket_utils::read_u8(fd) == 1) { - return fd; - } else { - close(fd); - return -1; - } + close(fd); + + return modules; + } + + int ConnectCompanion(size_t index) { + int fd = Connect(1); + if (fd == -1) { + PLOGE("ConnectCompanion"); + + return -1; } - int GetModuleDir(size_t index) { - UniqueFd fd = Connect(1); - if (fd == -1) { - PLOGE("GetModuleDir"); - return -1; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir); - socket_utils::write_usize(fd, index); - return socket_utils::recv_fd(fd); + socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket); + socket_utils::write_usize(fd, index); + + uint8_t res = socket_utils::read_u8(fd); + + if (res == 1) return fd; + else { + close(fd); + + return -1; } + } - void ZygoteRestart() { - UniqueFd fd = Connect(1); - if (fd == -1) { - if (errno == ENOENT) { - LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)"); - } else { - PLOGE("Could not notify ZygoteRestart"); - } - return; - } - if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) { - PLOGE("Failed to request ZygoteRestart"); - } + int GetModuleDir(size_t index) { + int fd = Connect(1); + if (fd == -1) { + PLOGE("GetModuleDir"); + + return -1; } - void SystemServerStarted() { - UniqueFd fd = Connect(1); - if (fd == -1) { - PLOGE("Failed to report system server started"); - } else { - if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) { - PLOGE("Failed to report system server started"); - } - } + socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir); + socket_utils::write_usize(fd, index); + int nfd = socket_utils::recv_fd(fd); + + close(fd); + + return nfd; + } + + void ZygoteRestart() { + int fd = Connect(1); + if (fd == -1) { + if (errno == ENOENT) LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)"); + else PLOGE("Could not notify ZygoteRestart"); + + return; } - void GetInfo(struct zygote_info *info) { - /* TODO: Optimize and avoid re-connect twice here */ - int fd = Connect(1); + if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) + PLOGE("Failed to request ZygoteRestart"); - if (fd != -1) { - info->running = true; + close(fd); + } - socket_utils::write_u8(fd, (uint8_t) SocketAction::GetInfo); + void SystemServerStarted() { + int fd = Connect(1); + if (fd == -1) PLOGE("Failed to report system server started"); + else { + if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) + PLOGE("Failed to report system server started"); + } - int flags = socket_utils::read_u32(fd); + close(fd); + } - if (flags & (1 << 27)) { - info->root_impl = ZYGOTE_ROOT_IMPL_APATCH; - } else if (flags & (1 << 29)) { - info->root_impl = ZYGOTE_ROOT_IMPL_KERNELSU; - } else if (flags & (1 << 30)) { - info->root_impl = ZYGOTE_ROOT_IMPL_MAGISK; - } else { - info->root_impl = ZYGOTE_ROOT_IMPL_NONE; - } + void GetInfo(struct zygote_info *info) { + /* TODO: Optimize and avoid re-connect twice here */ + int fd = Connect(1); - info->pid = socket_utils::read_u32(fd); + if (fd != -1) { + info->running = true; -info->modules = (struct zygote_modules *)malloc(sizeof(struct zygote_modules)); - if (info->modules == NULL) { - info->modules->modules_count = 0; + socket_utils::write_u8(fd, (uint8_t) SocketAction::GetInfo); - close(fd); + int flags = socket_utils::read_u32(fd); - return; - } + if (flags & (1 << 27)) { + info->root_impl = ZYGOTE_ROOT_IMPL_APATCH; + } else if (flags & (1 << 29)) { + info->root_impl = ZYGOTE_ROOT_IMPL_KERNELSU; + } else if (flags & (1 << 30)) { + info->root_impl = ZYGOTE_ROOT_IMPL_MAGISK; + } else { + info->root_impl = ZYGOTE_ROOT_IMPL_NONE; + } - info->modules->modules_count = socket_utils::read_usize(fd); + info->pid = socket_utils::read_u32(fd); - if (info->modules->modules_count == 0) { - info->modules->modules = NULL; + info->modules = (struct zygote_modules *)malloc(sizeof(struct zygote_modules)); + if (info->modules == NULL) { + info->modules->modules_count = 0; - close(fd); + close(fd); - return; - } + return; + } - info->modules->modules = (char **)malloc(sizeof(char *) * info->modules->modules_count); - if (info->modules->modules == NULL) { - free(info->modules); - info->modules = NULL; - info->modules->modules_count = 0; + info->modules->modules_count = socket_utils::read_usize(fd); - close(fd); + if (info->modules->modules_count == 0) { + info->modules->modules = NULL; - return; - } + close(fd); + + return; + } - for (size_t i = 0; i < info->modules->modules_count; i++) { - /* INFO by ThePedroo: Ugly solution to read with std::string existance (temporary) */ - std::string name = socket_utils::read_string(fd); + info->modules->modules = (char **)malloc(sizeof(char *) * info->modules->modules_count); + if (info->modules->modules == NULL) { + free(info->modules); + info->modules = NULL; + info->modules->modules_count = 0; - char module_path[PATH_MAX]; - snprintf(module_path, sizeof(module_path), "/data/adb/modules/%s/module.prop", name.c_str()); + close(fd); - FILE *module_prop = fopen(module_path, "r"); - if (module_prop == NULL) { - info->modules->modules[i] = strdup(name.c_str()); - } else { - char line[1024]; - while (fgets(line, sizeof(line), module_prop) != NULL) { - if (strncmp(line, "name=", 5) == 0) { - info->modules->modules[i] = strndup(line + 5, strlen(line) - 6); + return; + } - break; - } - } + for (size_t i = 0; i < info->modules->modules_count; i++) { + /* INFO by ThePedroo: Ugly solution to read with std::string existance (temporary) */ + std::string name = socket_utils::read_string(fd); - fclose(module_prop); + char module_path[PATH_MAX]; + snprintf(module_path, sizeof(module_path), "/data/adb/modules/%s/module.prop", name.c_str()); + + FILE *module_prop = fopen(module_path, "r"); + if (module_prop == NULL) { + info->modules->modules[i] = strdup(name.c_str()); + } else { + char line[1024]; + while (fgets(line, sizeof(line), module_prop) != NULL) { + if (strncmp(line, "name=", 5) == 0) { + info->modules->modules[i] = strndup(line + 5, strlen(line) - 6); + + break; + } } + + fclose(module_prop); } + } - close(fd); - } else info->running = false; - } + close(fd); + } else info->running = false; + } } diff --git a/loader/src/common/socket_utils.cpp b/loader/src/common/socket_utils.cpp index d4a9f1d5..730d49d6 100644 --- a/loader/src/common/socket_utils.cpp +++ b/loader/src/common/socket_utils.cpp @@ -5,131 +5,133 @@ #include "socket_utils.h" namespace socket_utils { - - ssize_t xread(int fd, void* buf, size_t count) { - size_t read_sz = 0; - ssize_t ret; - do { - ret = read(fd, (std::byte*) buf + read_sz, count - read_sz); - if (ret < 0) { - if (errno == EINTR) continue; - PLOGE("read"); - return ret; - } - read_sz += ret; - } while (read_sz != count && ret != 0); - if (read_sz != count) { - PLOGE("read (%zu != %zu)", count, read_sz); - } - return read_sz; - } - - size_t xwrite(int fd, const void* buf, size_t count) { - size_t write_sz = 0; - ssize_t ret; - do { - ret = write(fd, (std::byte*) buf + write_sz, count - write_sz); - if (ret < 0) { - if (errno == EINTR) continue; - PLOGE("write"); - return write_sz; - } - write_sz += ret; - } while (write_sz != count && ret != 0); - if (write_sz != count) { - PLOGE("write (%zu != %zu)", count, write_sz); - } - return write_sz; - } - - ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) { - int rec = recvmsg(sockfd, msg, flags); - if (rec < 0) PLOGE("recvmsg"); - return rec; - } - - void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) { - iovec iov = { - .iov_base = &cnt, - .iov_len = sizeof(cnt), - }; - msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = cmsgbuf, - .msg_controllen = bufsz - }; - - xrecvmsg(sockfd, &msg, MSG_WAITALL); - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - - if (msg.msg_controllen != bufsz || - cmsg == nullptr || - // TODO: pass from rust: 20, expected: 16 - // cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || - cmsg->cmsg_level != SOL_SOCKET || - cmsg->cmsg_type != SCM_RIGHTS) { - return nullptr; + ssize_t xread(int fd, void* buf, size_t count) { + size_t read_sz = 0; + ssize_t ret; + do { + ret = read(fd, (std::byte*) buf + read_sz, count - read_sz); + if (ret < 0) { + if (errno == EINTR) continue; + PLOGE("read"); + return ret; + } + read_sz += ret; + } while (read_sz != count && ret != 0); + if (read_sz != count) { + PLOGE("read (%zu != %zu)", count, read_sz); + } + return read_sz; + } + + size_t xwrite(int fd, const void* buf, size_t count) { + size_t write_sz = 0; + ssize_t ret; + do { + ret = write(fd, (std::byte*) buf + write_sz, count - write_sz); + if (ret < 0) { + if (errno == EINTR) continue; + PLOGE("write"); + return write_sz; } - - return CMSG_DATA(cmsg); - } - - template - inline T read_exact_or(int fd, T fail) { - T res; - return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail; - } - - template - inline bool write_exact(int fd, T val) { - return sizeof(T) == xwrite(fd, &val, sizeof(T)); - } - - uint8_t read_u8(int fd) { - return read_exact_or(fd, 0); + write_sz += ret; + } while (write_sz != count && ret != 0); + if (write_sz != count) { + PLOGE("write (%zu != %zu)", count, write_sz); } + return write_sz; + } + + ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) { + int rec = recvmsg(sockfd, msg, flags); + if (rec < 0) PLOGE("recvmsg"); + return rec; + } + + template + inline T read_exact_or(int fd, T fail) { + T res; + return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail; + } + + template + inline bool write_exact(int fd, T val) { + return sizeof(T) == xwrite(fd, &val, sizeof(T)); + } + + uint8_t read_u8(int fd) { + return read_exact_or(fd, 0); + } + + uint32_t read_u32(int fd) { + return read_exact_or(fd, 0); + } + + size_t read_usize(int fd) { + return read_exact_or(fd, 0); + } + + bool write_usize(int fd, size_t val) { + return write_exact(fd, val); + } + + std::string read_string(int fd) { + size_t len = read_usize(fd); + + char buf[len + 1]; + xread(fd, buf, len); + + buf[len] = '\0'; + + return buf; + } + + bool write_u8(int fd, uint8_t val) { + return write_exact(fd, val); + } + + void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) { + iovec iov = { + .iov_base = &cnt, + .iov_len = sizeof(cnt), + }; + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = bufsz + }; + + xrecvmsg(sockfd, &msg, MSG_WAITALL); + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + + if (msg.msg_controllen != bufsz || + cmsg == nullptr || + // TODO: pass from rust: 20, expected: 16 + // cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { + return nullptr; + } + + return CMSG_DATA(cmsg); + } - uint32_t read_u32(int fd) { - return read_exact_or(fd, 0); - } - - size_t read_usize(int fd) { - return read_exact_or(fd, 0); - } - - bool write_usize(int fd, size_t val) { - return write_exact(fd, val); - } - - std::string read_string(int fd) { - auto len = read_usize(fd); - char buf[len + 1]; - buf[len] = '\0'; - xread(fd, buf, len); - return buf; - } - - bool write_u8(int fd, uint8_t val) { - return write_exact(fd, val); - } + int recv_fd(int sockfd) { + char cmsgbuf[CMSG_SPACE(sizeof(int))]; - bool write_u32(int fd, uint32_t val) { - return write_exact(fd, val); - } + void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); + if (data == nullptr) return -1; - bool write_string(int fd, std::string_view str) { - return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size()); - } + int result; + memcpy(&result, data, sizeof(int)); + return result; + } - int recv_fd(int sockfd) { - char cmsgbuf[CMSG_SPACE(sizeof(int))]; - - void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); - if (data == nullptr) return -1; + bool write_u32(int fd, uint32_t val) { + return write_exact(fd, val); + } - int result; - memcpy(&result, data, sizeof(int)); - return result; - } + bool write_string(int fd, std::string_view str) { + return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size()); + } } diff --git a/loader/src/include/solist.hpp b/loader/src/include/solist.hpp index 408715eb..5feb94d3 100644 --- a/loader/src/include/solist.hpp +++ b/loader/src/include/solist.hpp @@ -62,7 +62,7 @@ namespace SoList for (auto *iter = solist; iter; iter = iter->get_next()) { if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_name)) { iter->nullify_path(); - LOGI("Cleared SOList entry for %s\n", target_name); + LOGI("Cleared SOList entry for %s", target_name); } } diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 5affa62f..4f02f6bf 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -184,7 +184,7 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { if (gettid() != getpid()) return res; - LOGV("pthread_attr_destroy\n"); + LOGV("pthread_attr_destroy"); if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { @@ -202,7 +202,7 @@ void initialize_jni_hook(); DCL_HOOK_FUNC(char *, strdup, const char *s) { if (strcmp(s, "com.android.internal.os.ZygoteInit") == 0) { - LOGV("strdup %s\n", s); + LOGV("strdup %s", s); initialize_jni_hook(); } @@ -271,7 +271,7 @@ void initialize_jni_hook() { if (!map.path.ends_with("/libnativehelper.so")) continue; void *h = dlopen(map.path.data(), RTLD_LAZY); if (!h) { - LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); + LOGW("cannot dlopen libnativehelper.so: %s", dlerror()); break; } get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); @@ -279,7 +279,7 @@ void initialize_jni_hook() { break; } if (!get_created_java_vms) { - LOGW("JNI_GetCreatedJavaVMs not found\n"); + LOGW("JNI_GetCreatedJavaVMs not found"); return; } } @@ -585,7 +585,7 @@ void ZygiskContext::run_modules_post() { // Remove from SoList to avoid detection bool solist_res = SoList::Initialize(); if (!solist_res) { - LOGE("Failed to initialize SoList\n"); + LOGE("Failed to initialize SoList"); } else { SoList::NullifySoName("jit-cache"); } @@ -599,7 +599,7 @@ void ZygiskContext::run_modules_post() { // MAP_SHARED should fix the suspicious mapping. void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); if (copy == MAP_FAILED) { - LOGE("Failed to mmap jit-cache-zygisk\n"); + LOGE("Failed to mmap jit-cache-zygisk"); continue; } @@ -674,7 +674,7 @@ void ZygiskContext::app_specialize_pre() { } 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.\n"); + LOGI("Manager process detected. Notifying that Zygisk has been enabled."); setenv("ZYGISK_ENABLED", "1", 1); } else { @@ -705,20 +705,20 @@ bool ZygiskContext::exempt_fd(int fd) { void ZygiskContext::nativeSpecializeAppProcess_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); - LOGV("pre specialize [%s]\n", process); + LOGV("pre specialize [%s]", process); // App specialize does not check FD flags[SKIP_FD_SANITIZATION] = true; app_specialize_pre(); } void ZygiskContext::nativeSpecializeAppProcess_post() { - LOGV("post specialize [%s]\n", process); + LOGV("post specialize [%s]", process); app_specialize_post(); } /* Zygisksu changed: No system_server status write back */ void ZygiskContext::nativeForkSystemServer_pre() { - LOGV("pre forkSystemServer\n"); + LOGV("pre forkSystemServer"); flags[SERVER_FORK_AND_SPECIALIZE] = true; fork_pre(); @@ -733,7 +733,7 @@ void ZygiskContext::nativeForkSystemServer_pre() { void ZygiskContext::nativeForkSystemServer_post() { if (pid == 0) { - LOGV("post forkSystemServer\n"); + LOGV("post forkSystemServer"); run_modules_post(); } fork_post(); @@ -741,7 +741,7 @@ void ZygiskContext::nativeForkSystemServer_post() { void ZygiskContext::nativeForkAndSpecialize_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); - LOGV("pre forkAndSpecialize [%s]\n", process); + 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+ */ @@ -758,7 +758,7 @@ void ZygiskContext::nativeForkAndSpecialize_pre() { void ZygiskContext::nativeForkAndSpecialize_post() { if (pid == 0) { - LOGV("post forkAndSpecialize [%s]\n", process); + LOGV("post forkAndSpecialize [%s]", process); app_specialize_post(); } fork_post(); @@ -780,7 +780,7 @@ ZygiskContext::~ZygiskContext() { if (!methods.empty() && env->RegisterNatives( env->FindClass(clz.data()), methods.data(), static_cast(methods.size())) != 0) { - LOGE("Failed to restore JNI hook of class [%s]\n", clz.data()); + LOGE("Failed to restore JNI hook of class [%s]", clz.data()); should_unmap_zygisk = false; } } @@ -801,14 +801,14 @@ static bool hook_commit() { if (lsplt::CommitHook()) { return true; } else { - LOGE("plt_hook failed\n"); + LOGE("plt_hook failed"); return false; } } 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)) { - LOGE("Failed to register plt_hook \"%s\"\n", symbol); + LOGE("Failed to register plt_hook \"%s\"", symbol); return; } plt_hook_list->emplace_back(dev, inode, symbol, old_func); @@ -872,12 +872,12 @@ 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)) { - LOGE("Failed to register plt_hook [%s]\n", sym); + LOGE("Failed to register plt_hook [%s]", sym); } } delete plt_hook_list; if (!hook_commit()) { - LOGE("Failed to restore plt_hook\n"); + LOGE("Failed to restore plt_hook"); should_unmap_zygisk = false; } } diff --git a/loader/src/ptracer/main.cpp b/loader/src/ptracer/main.cpp index 9cc507d9..3e833d64 100644 --- a/loader/src/ptracer/main.cpp +++ b/loader/src/ptracer/main.cpp @@ -82,39 +82,6 @@ int main(int argc, char **argv) { } } - #ifdef __LP64__ - printf("Daemon64 running: %d\n", status64.daemon_running); - printf("Zygote64 injected: %s\n", status64.zygote_injected ? "yes" : "no"); - #else - printf("Daemon32 running: %s\n", status32.daemon_running ? "yes" : "no"); - printf("Zygote32 injected: %s\n", status32.zygote_injected ? "yes" : "no"); - #endif - - switch (tracing_state) { - case TRACING: { - printf("Tracing state: TRACING\n"); - - break; - } - case STOPPING: { - printf("Tracing state: STOPPING\n"); - printf("Stop reason: %s\n", monitor_stop_reason); - - break; - } - case STOPPED: { - printf("Tracing state: STOPPED\n"); - printf("Stop reason: %s\n", monitor_stop_reason); - - break; - } - case EXITING: { - printf("Tracing state: EXITING\n"); - - break; - } - } - if (info.modules->modules_count != 0) { printf("Modules: %zu\n", info.modules->modules_count); diff --git a/loader/src/ptracer/monitor.cpp b/loader/src/ptracer/monitor.cpp index 318f50c1..805b2c0c 100644 --- a/loader/src/ptracer/monitor.cpp +++ b/loader/src/ptracer/monitor.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -103,9 +102,25 @@ struct EventLoop { } }; +enum TracingState { + TRACING = 1, + STOPPING, + STOPPED, + EXITING +}; + TracingState tracing_state = TRACING; static char prop_path[PATH_MAX]; +struct Status { + bool supported = false; + bool zygote_injected = false; + bool daemon_running = false; + pid_t daemon_pid = -1; + char *daemon_info; + char *daemon_error_info; +}; + Status status64; Status status32; @@ -149,13 +164,10 @@ struct SocketHandler : public EventHandler { }; while (1) { - std::vector buf; - buf.resize(sizeof(MsgHead), 0); - - MsgHead &msg = *((MsgHead *)buf.data()); + struct MsgHead *msg = (struct MsgHead *)malloc(sizeof(struct MsgHead)); ssize_t real_size; - ssize_t nread = recv(sock_fd_, &msg, sizeof(msg), MSG_PEEK); + ssize_t nread = recv(sock_fd_, msg, sizeof(struct MsgHead), MSG_PEEK); if (nread == -1) { if (errno == EAGAIN) break; @@ -167,17 +179,17 @@ struct SocketHandler : public EventHandler { continue; } - if (msg.cmd >= Command::DAEMON64_SET_INFO && msg.cmd != Command::SYSTEM_SERVER_STARTED) { + if (msg->cmd >= Command::DAEMON64_SET_INFO && msg->cmd != Command::SYSTEM_SERVER_STARTED) { if (nread != sizeof(msg)) { - LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(MsgHead)); + LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(MsgHead)); continue; } - real_size = sizeof(MsgHead) + msg.length; + real_size = sizeof(MsgHead) + msg->length; } else { if (nread != sizeof(Command)) { - LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(Command)); + LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(Command)); continue; } @@ -185,8 +197,8 @@ struct SocketHandler : public EventHandler { real_size = sizeof(Command); } - buf.resize(real_size); - nread = recv(sock_fd_, &msg, real_size, 0); + msg = (struct MsgHead *)realloc(msg, real_size); + nread = recv(sock_fd_, msg, real_size, 0); if (nread == -1) { if (errno == EAGAIN) break; @@ -201,7 +213,7 @@ struct SocketHandler : public EventHandler { continue; } - switch (msg.cmd) { + switch (msg->cmd) { case START: { if (tracing_state == STOPPING) tracing_state = TRACING; else if (tracing_state == STOPPED) { @@ -255,7 +267,7 @@ struct SocketHandler : public EventHandler { break; } case DAEMON64_SET_INFO: { - LOGD("received daemon64 info %s", msg.data); + LOGD("received daemon64 info %s", msg->data); /* Will only happen if somehow the daemon restarts */ if (status64.daemon_info != NULL) { @@ -263,32 +275,42 @@ struct SocketHandler : public EventHandler { status64.daemon_info = NULL; } - status64.daemon_info = (char *)malloc(msg.length); - memcpy(status64.daemon_info, msg.data, msg.length - 1); - status64.daemon_info[msg.length - 1] = '\0'; + status64.daemon_info = (char *)malloc(msg->length); + if (status64.daemon_info == NULL) { + PLOGE("malloc daemon64 info"); + + break; + } + + strcpy(status64.daemon_info, msg->data); updateStatus(); break; } case DAEMON32_SET_INFO: { - LOGD("received daemon32 info %s", msg.data); + LOGD("received daemon32 info %s", msg->data); if (status32.daemon_info != NULL) { free(status32.daemon_info); status32.daemon_info = NULL; } - status32.daemon_info = (char *)malloc(msg.length); - memcpy(status32.daemon_info, msg.data, msg.length - 1); - status32.daemon_info[msg.length - 1] = '\0'; + status32.daemon_info = (char *)malloc(msg->length); + if (status32.daemon_info == NULL) { + PLOGE("malloc daemon32 info"); + + break; + } + + strcpy(status32.daemon_info, msg->data); updateStatus(); break; } case DAEMON64_SET_ERROR_INFO: { - LOGD("received daemon64 error info %s", msg.data); + LOGD("received daemon64 error info %s", msg->data); status64.daemon_running = false; @@ -297,16 +319,21 @@ struct SocketHandler : public EventHandler { status64.daemon_error_info = NULL; } - status64.daemon_error_info = (char *)malloc(msg.length); - memcpy(status64.daemon_error_info, msg.data, msg.length - 1); - status64.daemon_error_info[msg.length - 1] = '\0'; + status64.daemon_error_info = (char *)malloc(msg->length); + if (status64.daemon_error_info == NULL) { + PLOGE("malloc daemon64 error info"); + + break; + } + + strcpy(status64.daemon_error_info, msg->data); updateStatus(); break; } case DAEMON32_SET_ERROR_INFO: { - LOGD("received daemon32 error info %s", msg.data); + LOGD("received daemon32 error info %s", msg->data); status32.daemon_running = false; @@ -315,9 +342,14 @@ struct SocketHandler : public EventHandler { status32.daemon_error_info = NULL; } - status32.daemon_error_info = (char *)malloc(msg.length); - memcpy(status32.daemon_error_info, msg.data, msg.length - 1); - status32.daemon_error_info[msg.length - 1] = '\0'; + status32.daemon_error_info = (char *)malloc(msg->length); + if (status32.daemon_error_info == NULL) { + PLOGE("malloc daemon32 error info"); + + break; + } + + strcpy(status32.daemon_error_info, msg->data); updateStatus(); @@ -333,6 +365,8 @@ struct SocketHandler : public EventHandler { break; } } + + free(msg); } } @@ -403,19 +437,27 @@ static bool ensure_daemon_created(bool is_64bit) { } } -#define CHECK_DAEMON_EXIT(abi) \ - if (status##abi.supported && pid == status64.daemon_pid) { \ - char status_str[64]; \ - parse_status(status, status_str, sizeof(status_str)); \ - \ - LOGW("daemon" #abi "pid %d exited: %s", pid, status_str); \ - status##abi.daemon_running = false; \ - \ - if (status##abi.daemon_error_info[0] == '\0') \ - memcpy(status##abi.daemon_error_info, status_str, strlen(status_str)); \ - \ - updateStatus(); \ - continue; \ +#define CHECK_DAEMON_EXIT(abi) \ + if (status##abi.supported && pid == status64.daemon_pid) { \ + char status_str[64]; \ + parse_status(status, status_str, sizeof(status_str)); \ + \ + LOGW("daemon" #abi " pid %d exited: %s", pid, status_str); \ + status##abi.daemon_running = false; \ + \ + if (!status##abi.daemon_error_info) { \ + status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \ + if (status##abi.daemon_error_info) { \ + LOGE("malloc daemon" #abi " error info failed"); \ + \ + return; \ + } \ + \ + memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \ + } \ + \ + updateStatus(); \ + continue; \ } #define PRE_INJECT(abi, is_64) \ @@ -641,14 +683,14 @@ static char post_section[1024]; #define WRITE_STATUS_ABI(suffix) \ if (status ## suffix.supported) { \ - strcat(status_text, " zygote" # suffix ":"); \ + strcat(status_text, " zygote" # suffix ": "); \ if (tracing_state != TRACING) strcat(status_text, "❓ unknown, "); \ else if (status ## suffix.zygote_injected) strcat(status_text, "😋 injected, "); \ else strcat(status_text, "❌ not injected, "); \ \ - strcat(status_text, " daemon" # suffix ":"); \ + strcat(status_text, "daemon" # suffix ": "); \ if (status ## suffix.daemon_running) { \ - strcat(status_text, "😋running"); \ + strcat(status_text, "😋 running "); \ \ if (status ## suffix.daemon_info != NULL) { \ strcat(status_text, "("); \ @@ -690,7 +732,7 @@ static void updateStatus() { } if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') { - strcat(status_text, "("); + strcat(status_text, " ("); strcat(status_text, monitor_stop_reason); strcat(status_text, ")"); } @@ -717,68 +759,26 @@ static bool prepare_environment() { return false; } - const char field_name[] = "description="; - - int pre_section_len = 0; - int post_section_len = 0; - - /* TODO: improve this code */ - int i = 1; - while (1) { - int int_char = fgetc(orig_prop); - if (int_char == EOF) break; - - pre_section[pre_section_len] = (char)int_char; - pre_section[pre_section_len + 1] = '\0'; - pre_section_len++; - - if ((char)int_char != field_name[0]) continue; - - while (1) { - int int_char2 = fgetc(orig_prop); - if (int_char2 == EOF) break; - - if ((char)int_char2 == field_name[i]) { - i++; - - if (i == (int)(sizeof(field_name) - 1)) { - pre_section[pre_section_len] = (char)int_char2; - pre_section[pre_section_len + 1] = '\0'; - pre_section_len++; - - while (1) { - int int_char3 = fgetc(orig_prop); - if (int_char3 == EOF) break; - - post_section[post_section_len] = (char)int_char3; - post_section[post_section_len + 1] = '\0'; - post_section_len++; - - i++; - } - - break; - } else { - pre_section[pre_section_len] = (char)int_char2; - pre_section[pre_section_len + 1] = '\0'; - pre_section_len++; - - continue; - } - } else { - pre_section[pre_section_len] = (char)int_char2; - pre_section[pre_section_len + 1] = '\0'; - pre_section_len++; + bool after_description = false; - i = 1; + char line[1024]; + while (fgets(line, sizeof(line), orig_prop) != NULL) { + if (strncmp(line, "description=", strlen("description=")) == 0) { + strcat(pre_section, "description="); + strcat(post_section, line + strlen("description=")); + after_description = true; - break; - } + continue; } + + if (after_description) strcat(post_section, line); + else strcat(pre_section, line); } fclose(orig_prop); + /* TODO: See if ZYGISK_ENABLED flag is already set, + if so, set a status saying to disable built-in Zygisk. */ updateStatus(); return true; @@ -786,7 +786,6 @@ static bool prepare_environment() { void init_monitor() { LOGI("ReZygisk %s", ZKSU_VERSION); - LOGI("init monitor started"); if (!prepare_environment()) exit(1); diff --git a/loader/src/ptracer/monitor.h b/loader/src/ptracer/monitor.h index 2eb1c3a1..c7db0f3c 100644 --- a/loader/src/ptracer/monitor.h +++ b/loader/src/ptracer/monitor.h @@ -3,29 +3,6 @@ #include -extern char monitor_stop_reason[32]; - -enum TracingState { - TRACING = 1, - STOPPING, - STOPPED, - EXITING -}; - -extern TracingState tracing_state; - -struct Status { - bool supported = false; - bool zygote_injected = false; - bool daemon_running = false; - pid_t daemon_pid = -1; - char *daemon_info; - char *daemon_error_info; -}; - -extern Status status64; -extern Status status32; - void init_monitor(); bool trace_zygote(int pid); diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.cpp index a92bb20f..9182ddde 100644 --- a/loader/src/ptracer/ptracer.cpp +++ b/loader/src/ptracer/ptracer.cpp @@ -100,7 +100,12 @@ bool inject_on_main(int pid, const char *lib_path) { For arm32 compatibility, we set the last bit to the same as the entry address */ - uintptr_t break_addr = (-0x05ec1cff & ~1) | ((uintptr_t)entry_addr & 1); + /* INFO: (-0x0F & ~1) is a value below zero, while the one after "|" + is an unsigned (must be 0 or greater) value, so we must + cast the second value to signed long (intptr_t) to avoid + undefined behavior. + */ + uintptr_t break_addr = (uintptr_t)((intptr_t)(-0x0F & ~1) | (intptr_t)((uintptr_t)entry_addr & 1)); if (!write_proc(pid, (uintptr_t)addr_of_entry_addr, &break_addr, sizeof(break_addr))) return false; ptrace(PTRACE_CONT, pid, 0, 0); @@ -110,7 +115,7 @@ bool inject_on_main(int pid, const char *lib_path) { if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) { if (!get_regs(pid, regs)) return false; - if (static_cast(regs.REG_IP & ~1) != (break_addr & ~1)) { + if (((int)regs.REG_IP & ~1) != ((int)break_addr & ~1)) { LOGE("stopped at unknown addr %p", (void *) regs.REG_IP); return false; @@ -184,8 +189,14 @@ bool inject_on_main(int pid, const char *lib_path) { } /* NOTICE: C++ -> C */ - char *err = (char *)malloc(dlerror_len + 1); - read_proc(pid, (uintptr_t) dlerror_str_addr, err, dlerror_len); + char *err = (char *)malloc((dlerror_len + 1) * sizeof(char)); + if (err == NULL) { + LOGE("malloc err"); + + return false; + } + + read_proc(pid, dlerror_str_addr, err, dlerror_len + 1); LOGE("dlerror info %s", err); diff --git a/loader/src/ptracer/utils.cpp b/loader/src/ptracer/utils.cpp index 70d327d0..1acc32c9 100644 --- a/loader/src/ptracer/utils.cpp +++ b/loader/src/ptracer/utils.cpp @@ -313,7 +313,11 @@ void *find_func_addr(std::vector &local_info, std::vector &rem /* WARNING: C++ keyword */ void align_stack(struct user_regs_struct ®s, long preserve) { - regs.REG_SP = (regs.REG_SP - preserve) & ~0xf; + /* INFO: ~0xf is a negative value, and REG_SP is unsigned, + so we must cast REG_SP to signed type before subtracting + then cast back to unsigned type. + */ + regs.REG_SP = (uintptr_t)((intptr_t)(regs.REG_SP - preserve) & ~0xf); } /* WARNING: C++ keyword */ diff --git a/module/build.gradle.kts b/module/build.gradle.kts index b9d327ad..a9c52fb0 100644 --- a/module/build.gradle.kts +++ b/module/build.gradle.kts @@ -80,7 +80,7 @@ androidComponents.onVariants { variant -> filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) } into("bin") { - from(project(":zygiskd").layout.buildDirectory.file("rustJniLibs/android")) + from(project(":zygiskd").layout.buildDirectory.getAsFile().get()) include("**/zygiskd") } into("lib") { @@ -100,7 +100,6 @@ androidComponents.onVariants { variant -> val privKey = kf.generatePrivate(privKeySpec); val sig = Signature.getInstance("ed25519") fun File.sha(realFile: File? = null) { - val path = this.path.replace("\\", "/") sig.update(this.name.toByteArray()) sig.update(0) // null-terminated string val real = realFile ?: this diff --git a/zygiskd/.cargo/config.toml b/zygiskd/.cargo/config.toml deleted file mode 100644 index 10816b18..00000000 --- a/zygiskd/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build] -target-dir = "build/intermediates/rust" -target = "aarch64-linux-android" diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml deleted file mode 100644 index bebacf3b..00000000 --- a/zygiskd/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "zygiskd" -authors = ["Nullptr"] -version = "1.0.0" -edition = "2021" -rust-version = "1.69" - -[dependencies] -csv = "1.3.0" -serde = { version = "1.0.130", features = ["derive"] } -android_logger = "0.13" -anyhow = { version = "1.0", features = ["backtrace"] } -bitflags = { version = "2.3" } -const_format = "0.2" -futures = "0.3" -konst = "0.3" -lazy_static = "1.4" -libc = "0.2" -log = "0.4" -memfd = "0.6" -num_enum = "0.5" -passfd = "0.1" -proc-maps = "0.3" - -rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] } -tokio = { version = "1.28", features = ["full"] } - -[profile.dev] -strip = false -panic = "abort" - -[profile.release] -strip = false -debug = true -panic = "abort" -opt-level = "z" -lto = true diff --git a/zygiskd/build.gradle.kts b/zygiskd/build.gradle.kts index 536393c9..033cb8a5 100644 --- a/zygiskd/build.gradle.kts +++ b/zygiskd/build.gradle.kts @@ -1,6 +1,21 @@ -plugins { - alias(libs.plugins.agp.lib) - alias(libs.plugins.rust.android) +import java.nio.file.Paths +import org.gradle.internal.os.OperatingSystem + +fun getLatestNDKPath(): String { + val android_home = System.getenv("ANDROID_HOME") + if (android_home == null) { + throw Exception("ANDROID_HOME not set") + } + + val ndkPath = android_home + "/ndk" + + val ndkDir = Paths.get(ndkPath) + if (!ndkDir.toFile().exists()) { + throw Exception("NDK not found at $ndkPath") + } + + val ndkVersion = ndkDir.toFile().listFiles().filter { it.isDirectory }.map { it.name }.sorted().last() + return ndkPath + "/" + ndkVersion } val minAPatchVersion: Int by rootProject.extra @@ -11,60 +26,95 @@ val verCode: Int by rootProject.extra val verName: String by rootProject.extra val commitHash: String by rootProject.extra -android.buildFeatures { - androidResources = false - buildConfig = false -} +val CStandardFlags = arrayOf( + "-D_GNU_SOURCE", "-std=c99", "-Wpedantic", "-Wall", "-Wextra", "-Werror", + "-Wformat", "-Wuninitialized", "-Wshadow", "-Wno-zero-length-array", + "-Wno-fixed-enum-extension", "-Iroot_impl", "-llog", + "-DMIN_APATCH_VERSION=$minAPatchVersion", + "-DMIN_KSU_VERSION=$minKsuVersion", + "-DMAX_KSU_VERSION=$maxKsuVersion", + "-DMIN_MAGISK_VERSION=$minMagiskVersion", + "-DZKSU_VERSION=\"$verName\"" +) + +val CFlagsRelease = arrayOf( + "-Wl,--strip-all", "-flto=thin", "-Ofast" +) + +val CFlagsDebug = arrayOf( + "-g", "-O0" +) -cargo { - module = "." - pythonCommand = "python3" - libname = "zygiskd" - targetIncludes = arrayOf("zygiskd") - targets = listOf("arm64", "arm", "x86", "x86_64") - targetDirectory = "build/intermediates/rust" - val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } - profile = if (isDebug) "debug" else "release" - exec = { spec, _ -> - spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path) - spec.environment("MIN_APATCH_VERSION", minAPatchVersion) - spec.environment("MIN_KSU_VERSION", minKsuVersion) - spec.environment("MAX_KSU_VERSION", maxKsuVersion) - spec.environment("MIN_MAGISK_VERSION", minMagiskVersion) - spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile") +val Files = arrayOf( + "root_impl/apatch.c", + "root_impl/common.c", + "root_impl/kernelsu.c", + "root_impl/magisk.c", + "companion.c", + "dl.c", + "main.c", + "utils.c", + "zygiskd.c" +) + +task("buildAndStrip") { + group = "build" + description = "Build the native library and strip the debug symbols." + + val isDebug = gradle.startParameter.taskNames.any { it.lowercase().contains("debug") } + doLast { + val ndkPath = getLatestNDKPath() + + val aarch64Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "aarch64-linux-android34-clang").toString() + val armv7aCompiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "armv7a-linux-androideabi34-clang").toString() + val x86Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "i686-linux-android34-clang").toString() + val x86_64Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "x86_64-linux-android34-clang").toString() + + if (!Paths.get(aarch64Compiler).toFile().exists()) { + throw Exception("aarch64 compiler not found at $aarch64Compiler") + } + + if (!Paths.get(armv7aCompiler).toFile().exists()) { + throw Exception("armv7a compiler not found at $armv7aCompiler") } -} -afterEvaluate { - task("buildAndStrip") { - dependsOn(":zygiskd:cargoBuild") - val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } - doLast { - val dir = File(buildDir, "rustJniLibs/android") - val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first() - val binDir = File(prebuilt, "bin") - val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}") - symbolDir.mkdirs() - val suffix = if (prebuilt.name.contains("windows")) ".exe" else "" - val strip = File(binDir, "llvm-strip$suffix") - val objcopy = File(binDir, "llvm-objcopy$suffix") - dir.listFiles()!!.forEach { - if (!it.isDirectory) return@forEach - val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug") - symbolPath.parentFile.mkdirs() - exec { - workingDir = it - commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath) - } - exec { - workingDir = it - commandLine(strip, "--strip-all", "zygiskd") - } - exec { - workingDir = it - commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd") - } - } - } + if (!Paths.get(x86Compiler).toFile().exists()) { + throw Exception("x86 compiler not found at $x86Compiler") } + + if (!Paths.get(x86_64Compiler).toFile().exists()) { + throw Exception("x86_64 compiler not found at $x86_64Compiler") + } + + val Files = Files.map { Paths.get(project.projectDir.toString(), "src", it).toString() }.toTypedArray() + + val buildDir = getLayout().getBuildDirectory().getAsFile().get() + buildDir.mkdirs() + + val aarch64OutputDir = Paths.get(buildDir.toString(), "arm64-v8a").toFile() + val armv7aOutputDir = Paths.get(buildDir.toString(), "armeabi-v7a").toFile() + val x86OutputDir = Paths.get(buildDir.toString(), "x86").toFile() + val x86_64OutputDir = Paths.get(buildDir.toString(), "x86_64").toFile() + + aarch64OutputDir.mkdirs() + armv7aOutputDir.mkdirs() + x86OutputDir.mkdirs() + x86_64OutputDir.mkdirs() + + val compileArgs = (if (isDebug) CFlagsDebug else CFlagsRelease) + CStandardFlags + + exec { + commandLine(aarch64Compiler, "-o", Paths.get(aarch64OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) + } + exec { + commandLine(armv7aCompiler, "-o", Paths.get(armv7aOutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) + } + exec { + commandLine(x86Compiler, "-o", Paths.get(x86OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) + } + exec { + commandLine(x86_64Compiler, "-o", Paths.get(x86_64OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) + } + } } + diff --git a/zygiskd/src/.gitignore b/zygiskd/src/.gitignore new file mode 100644 index 00000000..8f961c38 --- /dev/null +++ b/zygiskd/src/.gitignore @@ -0,0 +1,2 @@ +zygiskd64 +zygiskd32 \ No newline at end of file diff --git a/zygiskd/src/LICENSE b/zygiskd/src/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/zygiskd/src/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/zygiskd/src/companion.c b/zygiskd/src/companion.c new file mode 100644 index 00000000..486b6fa2 --- /dev/null +++ b/zygiskd/src/companion.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "companion.h" +#include "dl.h" +#include "utils.h" + +typedef void (*zygisk_companion_entry_func)(int); + +struct companion_module_thread_args { + int fd; + zygisk_companion_entry_func entry; +}; + +zygisk_companion_entry_func load_module(int fd) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + void *handle = android_dlopen(path, RTLD_NOW); + void *entry = dlsym(handle, "zygisk_companion_entry"); + if (entry == NULL) return NULL; + + return (zygisk_companion_entry_func)entry; +} + +void *entry_thread(void *arg) { + struct companion_module_thread_args *args = (struct companion_module_thread_args *)arg; + + int fd = args->fd; + zygisk_companion_entry_func module_entry = args->entry; + + module_entry(fd); + + close(fd); + free(args); + + pthread_exit(NULL); +} + +/* WARNING: Dynamic memory based */ +void companion_entry(int fd) { + LOGI("New companion entry.\n - Client fd: %d\n", fd); + + /* TODO: Use non-NULL string termination */ + char name[256 + 1]; + ssize_t name_length = read_string(fd, name, sizeof(name) - 1); + if (name_length == -1) { + LOGE("Failed to read module name\n"); + + ssize_t ret = write_uint8_t(fd, 2); + ASSURE_SIZE_WRITE("ZygiskdCompanion", "name", ret, sizeof(uint8_t)); + + exit(0); + } + name[name_length] = '\0'; + + LOGI(" - Module name: `%.*s`\n", (int)name_length, name); + + int library_fd = read_fd(fd); + ssize_t ret = 0; + if (library_fd == -1) { + LOGE("Failed to receive library fd\n"); + + ret = write_uint8_t(fd, 2); + ASSURE_SIZE_WRITE("ZygiskdCompanion", "library_fd", ret, sizeof(uint8_t)); + + exit(0); + } + + LOGI(" - Library fd: %d\n", library_fd); + + zygisk_companion_entry_func module_entry = load_module(library_fd); + close(library_fd); + + if (module_entry == NULL) { + LOGI("No companion module entry for module: %.*s\n", (int)name_length, name); + + ret = write_uint8_t(fd, 0); + ASSURE_SIZE_WRITE("ZygiskdCompanion", "module_entry", ret, sizeof(uint8_t)); + + exit(0); + } else { + ret = write_uint8_t(fd, 1); + ASSURE_SIZE_WRITE("ZygiskdCompanion", "module_entry", ret, sizeof(uint8_t)); + } + + while (1) { + if (!check_unix_socket(fd, true)) { + LOGI("Something went wrong in companion. Bye!\n"); + + exit(0); + + break; + } + + int client_fd = read_fd(fd); + if (fd == -1) { + LOGE("Failed to receive client fd\n"); + + exit(0); + } + + struct companion_module_thread_args *args = malloc(sizeof(struct companion_module_thread_args)); + if (args == NULL) { + LOGE("Failed to allocate memory for thread args\n"); + + exit(0); + } + + args->fd = client_fd; + args->entry = module_entry; + + LOGI("New companion request.\n - Module name: %.*s\n - Client fd: %d\n", (int)name_length, name, args->fd); + + ret = write_uint8_t(args->fd, 1); + ASSURE_SIZE_WRITE("ZygiskdCompanion", "client_fd", ret, sizeof(uint8_t)); + + pthread_t thread; + pthread_create(&thread, NULL, entry_thread, args); + } +} diff --git a/zygiskd/src/companion.h b/zygiskd/src/companion.h new file mode 100644 index 00000000..f19c5a86 --- /dev/null +++ b/zygiskd/src/companion.h @@ -0,0 +1,6 @@ +#ifndef COMPANION_H +#define COMPANION_H + +void companion_entry(int fd); + +#endif /* COMPANION_H */ diff --git a/zygiskd/src/companion.rs b/zygiskd/src/companion.rs deleted file mode 100644 index 8d158a5b..00000000 --- a/zygiskd/src/companion.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::dl; -use crate::utils::{check_unix_socket, UnixStreamExt}; -use anyhow::Result; -use passfd::FdPassingExt; -use rustix::fs::fstat; -use std::ffi::c_void; -use std::os::fd::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::net::UnixStream; -use std::thread; - -type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); - -pub fn entry(fd: i32) { - log::info!("companion entry fd={}", fd); - let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; - let name = stream.read_string().expect("read name"); - let library = stream.recv_fd().expect("receive library fd"); - let entry = load_module(library).expect("load module"); - unsafe { libc::close(library) }; - - let entry = match entry { - Some(entry) => { - log::debug!("Companion process created for `{name}`"); - stream.write_u8(1).expect("reply 1"); - entry - } - None => { - log::debug!("No companion entry for `{name}`"); - stream.write_u8(0).expect("reply 0"); - std::process::exit(0); - } - }; - - loop { - if !check_unix_socket(&stream, true) { - log::info!("Something bad happened in zygiskd, terminate companion"); - std::process::exit(0); - } - let fd = stream.recv_fd().expect("recv fd"); - log::trace!("New companion request from module `{name}` fd=`{fd}`"); - let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; - stream.write_u8(1).expect("reply success"); - thread::spawn(move || { - let st0 = fstat(&stream).expect("failed to stat stream"); - unsafe { - entry(stream.as_raw_fd()); - } - // 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 let Ok(st1) = fstat(&stream) { - if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino { - std::mem::forget(stream); - } - } - }); - } -} - -fn load_module(fd: RawFd) -> Result> { - unsafe { - let path = format!("/proc/self/fd/{fd}"); - let handle = dl::dlopen(&path, libc::RTLD_NOW)?; - let symbol = std::ffi::CString::new("zygisk_companion_entry")?; - let entry = libc::dlsym(handle, symbol.as_ptr()); - if entry.is_null() { - return Ok(None); - } - let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry); - Ok(Some(fnptr)) - } -} diff --git a/zygiskd/src/constants.h b/zygiskd/src/constants.h new file mode 100644 index 00000000..55f8a569 --- /dev/null +++ b/zygiskd/src/constants.h @@ -0,0 +1,57 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include + +#define bool _Bool +#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 + #define lp_select(a, b) a +#endif + +#define ZYGOTE_INJECTED lp_select(5, 4) +#define DAEMON_SET_INFO lp_select(7, 6) +#define DAEMON_SET_ERROR_INFO lp_select(9, 8) +#define SYSTEM_SERVER_STARTED 10 + +enum DaemonSocketAction { + PingHeartbeat, + RequestLogcatFd, + GetProcessFlags, + GetInfo, + ReadModules, + RequestCompanionSocket, + GetModuleDir, + ZygoteRestart, + SystemServerStarted +}; + +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_ROOT_IS_KSU = (1u << 29), + PROCESS_ROOT_IS_MAGISK = (1u << 30), + PROCESS_IS_SYS_UI = (1u << 31), + PROCESS_IS_SYSUI = (1u << 31) +}; + +enum RootImplState { + Supported, + TooOld, + Inexistent, + Abnormal +}; + +#endif /* CONSTANTS_H */ diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs deleted file mode 100644 index 4d8a93fe..00000000 --- a/zygiskd/src/constants.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::lp_select; -use bitflags::bitflags; -use konst::primitive::parse_i32; -use konst::unwrap_ctx; -use log::LevelFilter; -use num_enum::TryFromPrimitive; - -pub const MIN_APATCH_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_APATCH_VERSION"))); -pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION"))); -pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION"))); -pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION"))); -pub const ZKSU_VERSION: &str = env!("ZKSU_VERSION"); - -#[cfg(debug_assertions)] -pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; -#[cfg(not(debug_assertions))] -pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; - -pub const PATH_MODULES_DIR: &str = ".."; -pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4); -pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6); -pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8); -pub const SYSTEM_SERVER_STARTED: i32 = 10; - -#[derive(Debug, Eq, PartialEq, TryFromPrimitive)] -#[repr(u8)] -pub enum DaemonSocketAction { - PingHeartbeat, - RequestLogcatFd, - GetProcessFlags, - GetInfo, - ReadModules, - RequestCompanionSocket, - GetModuleDir, - ZygoteRestart, - SystemServerStarted, -} - -// Zygisk process flags -bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct ProcessFlags: u32 { - const PROCESS_GRANTED_ROOT = 1 << 0; - const PROCESS_ON_DENYLIST = 1 << 1; - const PROCESS_IS_MANAGER = 1 << 28; - const PROCESS_ROOT_IS_APATCH = 1 << 27; - const PROCESS_ROOT_IS_KSU = 1 << 29; - const PROCESS_ROOT_IS_MAGISK = 1 << 30; - const PROCESS_IS_SYSUI = 1 << 31; - } -} diff --git a/zygiskd/src/dl.c b/zygiskd/src/dl.c new file mode 100644 index 00000000..5018b0fa --- /dev/null +++ b/zygiskd/src/dl.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "companion.h" +#include "dl.h" +#include "utils.h" + +#define ANDROID_NAMESPACE_TYPE_SHARED 0x2 +#define ANDROID_DLEXT_USE_NAMESPACE 0x200 + +typedef struct AndroidNamespace { + unsigned char _unused[0]; +} AndroidNamespace; + +typedef struct AndroidDlextinfo { + uint64_t flags; + void *reserved_addr; + size_t reserved_size; + int relro_fd; + int library_fd; + off64_t library_fd_offset; + AndroidNamespace *library_namespace; +} AndroidDlextinfo; + +typedef AndroidNamespace *(*AndroidCreateNamespaceFn)( + const char *name, + const char *ld_library_path, + const char *default_library_path, + uint64_t type, + const char *permitted_when_isolated_path, + AndroidNamespace *parent, + const void *caller_addr +); + +extern void *android_dlopen_ext(const char *filename, int flags, const AndroidDlextinfo *extinfo); + +void *android_dlopen(char *path, int flags) { + char *dir = dirname(path); + struct AndroidDlextinfo info = { + .flags = 0, + .reserved_addr = NULL, + .reserved_size = 0, + .relro_fd = 0, + .library_fd = 0, + .library_fd_offset = 0, + .library_namespace = NULL, + }; + + void *handle = dlsym(RTLD_DEFAULT, "__loader_android_create_namespace"); + AndroidCreateNamespaceFn android_create_namespace_fn = (AndroidCreateNamespaceFn)handle; + + AndroidNamespace *ns = android_create_namespace_fn( + path, + dir, + NULL, + ANDROID_NAMESPACE_TYPE_SHARED, + NULL, + NULL, + (const void *)&android_dlopen + ); + + if (ns != NULL) { + info.flags = ANDROID_DLEXT_USE_NAMESPACE; + info.library_namespace = ns; + + LOGI("Open %s with namespace %p\n", path, (void *)ns); + } else { + LOGI("Cannot create namespace for %s\n", path); + } + + void *result = android_dlopen_ext(path, flags, &info); + if (result == NULL) { + LOGE("Failed to dlopen %s: %s\n", path, dlerror()); + } + + return result; +} diff --git a/zygiskd/src/dl.h b/zygiskd/src/dl.h new file mode 100644 index 00000000..97ba83e0 --- /dev/null +++ b/zygiskd/src/dl.h @@ -0,0 +1,6 @@ +#ifndef DL_H +#define DL_H + +void *android_dlopen(char *path, int flags); + +#endif /* DL_H */ diff --git a/zygiskd/src/dl.rs b/zygiskd/src/dl.rs deleted file mode 100644 index a401fcd5..00000000 --- a/zygiskd/src/dl.rs +++ /dev/null @@ -1,85 +0,0 @@ -use anyhow::{bail, Result}; -use std::ffi::{c_char, c_void}; - -pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2; -pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct AndroidNamespace { - _unused: [u8; 0], -} - -#[repr(C)] -pub struct AndroidDlextinfo { - pub flags: u64, - pub reserved_addr: *mut c_void, - pub reserved_size: libc::size_t, - pub relro_fd: libc::c_int, - pub library_fd: libc::c_int, - pub library_fd_offset: libc::off64_t, - pub library_namespace: *mut AndroidNamespace, -} - -extern "C" { - pub fn android_dlopen_ext( - filename: *const c_char, - flags: libc::c_int, - extinfo: *const AndroidDlextinfo, - ) -> *mut c_void; -} - -type AndroidCreateNamespaceFn = unsafe extern "C" fn( - *const c_char, // name - *const c_char, // ld_library_path - *const c_char, // default_library_path - u64, // type - *const c_char, // permitted_when_isolated_path - *mut AndroidNamespace, // parent - *const c_void, // caller_addr -) -> *mut AndroidNamespace; - -pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> { - let filename = std::ffi::CString::new(path)?; - let filename = filename.as_ptr() as *mut _; - let dir = libc::dirname(filename); - let mut info = AndroidDlextinfo { - flags: 0, - reserved_addr: std::ptr::null_mut(), - reserved_size: 0, - relro_fd: 0, - library_fd: 0, - library_fd_offset: 0, - library_namespace: std::ptr::null_mut(), - }; - - let android_create_namespace_fn = libc::dlsym( - libc::RTLD_DEFAULT, - std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(), - ); - let android_create_namespace_fn: AndroidCreateNamespaceFn = - std::mem::transmute(android_create_namespace_fn); - let ns = android_create_namespace_fn( - filename, - dir, - std::ptr::null(), - ANDROID_NAMESPACE_TYPE_SHARED, - std::ptr::null(), - std::ptr::null_mut(), - &dlopen as *const _ as *const c_void, - ); - if ns != std::ptr::null_mut() { - info.flags = ANDROID_DLEXT_USE_NAMESPACE; - info.library_namespace = ns; - log::debug!("Open {} with namespace {:p}", path, ns); - } else { - log::debug!("Cannot create namespace for {}", path); - }; - - let result = android_dlopen_ext(filename, flags, &info); - if result.is_null() { - let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy(); - bail!(e); - } - Ok(result) -} diff --git a/zygiskd/src/main.c b/zygiskd/src/main.c new file mode 100644 index 00000000..f1245cde --- /dev/null +++ b/zygiskd/src/main.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +#include + +#include "root_impl/common.h" +#include "companion.h" +#include "zygiskd.h" + +#include "utils.h" + +int __android_log_print(int prio, const char *tag, const char *fmt, ...); + +int main(int argc, char *argv[]) { + #ifdef __LP64__ + LOGI("Welcome to ReZygisk %s Zygiskd64!\n", ZKSU_VERSION); + #else + LOGI("Welcome to ReZygisk %s Zygiskd32!\n", ZKSU_VERSION); + #endif + + if (argc > 1) { + if (strcmp(argv[1], "companion") == 0) { + if (argc < 3) { + LOGI("Usage: zygiskd companion \n"); + + return 1; + } + + int fd = atoi(argv[2]); + companion_entry(fd); + + return 0; + } + + else if (strcmp(argv[1], "version") == 0) { + LOGI("ReZygisk Daemon %s\n", ZKSU_VERSION); + + return 0; + } + + else if (strcmp(argv[1], "root") == 0) { + root_impls_setup(); + + struct root_impl impl; + get_impl(&impl); + + char impl_name[LONGEST_ROOT_IMPL_NAME]; + stringify_root_impl_name(impl, impl_name); + + LOGI("Root implementation: %s\n", impl_name); + + return 0; + } + + else { + LOGI("Usage: zygiskd [companion|version|root]\n"); + + return 0; + } + } + + if (switch_mount_namespace((pid_t)1) == false) { + LOGE("Failed to switch mount namespace\n"); + + return 1; + } + root_impls_setup(); + zygiskd_start(argv); + + return 0; +} diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs deleted file mode 100644 index df042e7c..00000000 --- a/zygiskd/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod companion; -mod constants; -mod dl; -mod root_impl; -mod utils; -mod zygiskd; - -use crate::constants::ZKSU_VERSION; - -fn init_android_logger(tag: &str) { - android_logger::init_once( - android_logger::Config::default() - .with_max_level(constants::MAX_LOG_LEVEL) - .with_tag(tag), - ); -} - -fn start() { - let args: Vec = std::env::args().collect(); - if args.len() == 3 && args[1] == "companion" { - let fd: i32 = args[2].parse().unwrap(); - companion::entry(fd); - return; - } else if args.len() == 2 && args[1] == "version" { - println!("ReZygisk daemon {}", ZKSU_VERSION); - return; - } else if args.len() == 2 && args[1] == "root" { - root_impl::setup(); - println!("root impl: {:?}", root_impl::get_impl()); - return; - } - - utils::switch_mount_namespace(1).expect("switch mnt ns"); - root_impl::setup(); - log::info!("current root impl: {:?}", root_impl::get_impl()); - zygiskd::main().expect("zygiskd main"); -} - -fn main() { - let process = std::env::args().next().unwrap(); - let nice_name = process.split('/').last().unwrap(); - init_android_logger(nice_name); - - start(); -} diff --git a/zygiskd/src/root_impl/apatch.c b/zygiskd/src/root_impl/apatch.c new file mode 100644 index 00000000..fadac60c --- /dev/null +++ b/zygiskd/src/root_impl/apatch.c @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include + +#include "../constants.h" +#include "../utils.h" +#include "common.h" + +#include "apatch.h" + +void apatch_get_existence(struct root_impl_state *state) { + struct stat s; + if (stat("/data/adb/apd", &s) != 0) { + if (errno != ENOENT) { + LOGE("Failed to stat APatch apd binary: %s\n", strerror(errno)); + } + errno = 0; + + state->state = Inexistent; + + return; + } + + char *PATH = getenv("PATH"); + if (PATH == NULL) { + LOGE("Failed to get PATH environment variable: %s\n", strerror(errno)); + errno = 0; + + state->state = Inexistent; + + return; + } + + if (strstr(PATH, "/data/adb/ap/bin") == NULL) { + LOGE("APatch's APD binary is not in PATH\n"); + + state->state = Inexistent; + + return; + } + + char apatch_version[32]; + char *const argv[] = { "apd", "-V", NULL }; + + if (!exec_command(apatch_version, sizeof(apatch_version), "/data/adb/apd", argv)) { + LOGE("Failed to execute apd binary: %s\n", strerror(errno)); + errno = 0; + + state->state = Inexistent; + + return; + } + + int version = atoi(apatch_version + strlen("apd ")); + + if (version == 0) state->state = Abnormal; + else if (version >= MIN_APATCH_VERSION && version <= 999999) state->state = Supported; + else if (version >= 1 && version <= MIN_APATCH_VERSION - 1) state->state = TooOld; + else state->state = Abnormal; +} + +struct package_config { + uid_t uid; + bool root_granted; + bool umount_needed; +}; + +struct packages_config { + struct package_config *configs; + size_t size; +}; + +/* WARNING: Dynamic memory based */ +bool _apatch_get_package_config(struct packages_config *restrict config) { + config->configs = NULL; + config->size = 0; + + FILE *fp = fopen("/data/adb/ap/package_config", "r"); + if (fp == NULL) { + LOGE("Failed to open APatch's package_config: %s\n", strerror(errno)); + + return false; + } + + char line[1048]; + /* INFO: Skip the CSV header */ + if (fgets(line, sizeof(line), fp) == NULL) { + LOGE("Failed to read APatch's package_config header: %s\n", strerror(errno)); + + fclose(fp); + + return false; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + config->configs = realloc(config->configs, (config->size + 1) * sizeof(struct package_config)); + if (config->configs == NULL) { + LOGE("Failed to realloc APatch config struct: %s\n", strerror(errno)); + + fclose(fp); + + return false; + } + + strtok(line, ","); + + char *exclude_str = strtok(NULL, ","); + if (exclude_str == NULL) continue; + + char *allow_str = strtok(NULL, ","); + if (allow_str == NULL) continue; + + char *uid_str = strtok(NULL, ","); + if (uid_str == NULL) continue; + + config->configs[config->size].uid = atoi(uid_str); + config->configs[config->size].root_granted = strcmp(allow_str, "1") == 0; + config->configs[config->size].umount_needed = strcmp(exclude_str, "1") == 0; + + config->size++; + } + + fclose(fp); + + return true; +} + +void _apatch_free_package_config(struct packages_config *restrict config) { + free(config->configs); +} + +bool apatch_uid_granted_root(uid_t uid) { + struct packages_config config; + if (!_apatch_get_package_config(&config)) { + _apatch_free_package_config(&config); + + return false; + } + + for (size_t i = 0; i < config.size; i++) { + if (config.configs[i].uid != uid) continue; + + /* INFO: This allow us to copy the information to avoid use-after-free */ + bool root_granted = config.configs[i].root_granted; + + _apatch_free_package_config(&config); + + return root_granted; + } + + _apatch_free_package_config(&config); + + return false; +} + +bool apatch_uid_should_umount(uid_t uid) { + struct packages_config config; + if (!_apatch_get_package_config(&config)) { + _apatch_free_package_config(&config); + + return false; + } + + for (size_t i = 0; i < config.size; i++) { + if (config.configs[i].uid != uid) continue; + + /* INFO: This allow us to copy the information to avoid use-after-free */ + bool umount_needed = config.configs[i].umount_needed; + + _apatch_free_package_config(&config); + + return umount_needed; + } + + _apatch_free_package_config(&config); + + return false; +} + +bool apatch_uid_is_manager(uid_t uid) { + struct stat s; + if (stat("/data/user_de/0/me.bmax.apatch", &s) == -1) { + if (errno != ENOENT) { + LOGE("Failed to stat APatch manager data directory: %s\n", strerror(errno)); + } + errno = 0; + + return false; + } + + return s.st_uid == uid; +} diff --git a/zygiskd/src/root_impl/apatch.h b/zygiskd/src/root_impl/apatch.h new file mode 100644 index 00000000..74e20bf1 --- /dev/null +++ b/zygiskd/src/root_impl/apatch.h @@ -0,0 +1,14 @@ +#ifndef APATCH_H +#define APATCH_H + +#include "../constants.h" + +void apatch_get_existence(struct root_impl_state *state); + +bool apatch_uid_granted_root(uid_t uid); + +bool apatch_uid_should_umount(uid_t uid); + +bool apatch_uid_is_manager(uid_t uid); + +#endif diff --git a/zygiskd/src/root_impl/apatch.rs b/zygiskd/src/root_impl/apatch.rs deleted file mode 100644 index aeab57ff..00000000 --- a/zygiskd/src/root_impl/apatch.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::process::{Command, Stdio}; -use std::fs::File; -use std::io::{self, BufRead, BufReader}; -use serde::Deserialize; -use crate::constants::MIN_APATCH_VERSION; - -pub enum Version { - Supported, - TooOld, - Abnormal, -} - -fn parse_version(output: &str) -> i32 { - let mut version: Option = None; - for line in output.lines() { - if let Some(num) = line.trim().split_whitespace().last() { - if let Ok(v) = num.parse::() { - version = Some(v); - break; - } - } - } - version.unwrap_or_default() -} - -fn read_su_path() -> Result { - let file = File::open("/data/adb/ap/su_path")?; - let mut reader = BufReader::new(file); - let mut su_path = String::new(); - reader.read_line(&mut su_path)?; - Ok(su_path.trim().to_string()) -} - -pub fn get_apatch() -> Option { - let default_su_path = String::from("/system/bin/su"); - let su_path = read_su_path().ok()?; - - let output = Command::new(&su_path) - .arg("-v") - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .output() - .ok()?; - let stdout = String::from_utf8(output.stdout).ok()?; - if !stdout.contains("APatch") { - return None; - } - - let output1 = Command::new("/data/adb/apd") - .arg("-V") - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .output() - .ok()?; - let stdout1 = String::from_utf8(output1.stdout).ok()?; - if su_path == default_su_path { - let version = parse_version(&stdout1); - const MAX_OLD_VERSION: i32 = MIN_APATCH_VERSION - 1; - match version { - 0 => Some(Version::Abnormal), - v if v >= MIN_APATCH_VERSION && v <= 999999 => Some(Version::Supported), - v if v >= 1 && v <= MAX_OLD_VERSION => Some(Version::TooOld), - _ => None, - } - } else { - return None; - } -} - -#[derive(Deserialize)] -#[allow(dead_code)] -struct PackageConfig { - pkg: String, - exclude: i32, - allow: i32, - uid: i32, - to_uid: i32, - sctx: String, -} - -fn read_package_config() -> Result, std::io::Error> { - let file = File::open("/data/adb/ap/package_config")?; - let mut reader = csv::Reader::from_reader(file); - - let mut package_configs = Vec::new(); - for record in reader.deserialize() { - match record { - Ok(config) => package_configs.push(config), - Err(error) => { - log::warn!("Error deserializing record: {}", error); - } - } - } - - Ok(package_configs) -} - -pub fn uid_granted_root(uid: i32) -> bool { - match read_package_config() { - Ok(package_configs) => { - package_configs - .iter() - .find(|config| config.uid == uid) - .map(|config| config.allow == 1) - .unwrap_or(false) - } - Err(err) => { - log::warn!("Error reading package config: {}", err); - return false; - } - } -} - -pub fn uid_should_umount(uid: i32) -> bool { - match read_package_config() { - Ok(package_configs) => { - package_configs - .iter() - .find(|config| config.uid == uid) - .map(|config| { - match config.exclude { - 1 => true, - _ => false, - } - }) - .unwrap_or(false) - } - Err(err) => { - log::warn!("Error reading package configs: {}", err); - false - } - } -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.bmax.apatch") { - return s.st_uid == uid as u32; - } - false -} diff --git a/zygiskd/src/root_impl/common.c b/zygiskd/src/root_impl/common.c new file mode 100644 index 00000000..e679f556 --- /dev/null +++ b/zygiskd/src/root_impl/common.c @@ -0,0 +1,126 @@ +#include + +#include + +#include "../utils.h" +#include "kernelsu.h" +#include "apatch.h" +#include "magisk.h" + +#include "common.h" + +static struct root_impl impl; + +void root_impls_setup(void) { + struct root_impl_state state_ksu; + ksu_get_existence(&state_ksu); + + struct root_impl_state state_apatch; + apatch_get_existence(&state_apatch); + + struct root_impl_state state_magisk; + magisk_get_existence(&state_magisk); + + /* INFO: Check if it's only one supported, if not, it's multile and that's bad. + Remember that true here is equal to the integer 1. */ + if ((state_ksu.state == Supported ? 1 : 0) + (state_apatch.state == Supported ? 1 : 0) + (state_magisk.state == Supported ? 1 : 0) >= 2) { + impl.impl = Multiple; + } else if (state_ksu.state == Supported) { + impl.impl = KernelSU; + } else if (state_apatch.state == Supported) { + impl.impl = APatch; + } else if (state_magisk.state == Supported) { + impl.impl = Magisk; + impl.variant = state_magisk.variant; + } else { + impl.impl = None; + } + + switch (impl.impl) { + case None: { + LOGI("No root implementation found.\n"); + + break; + } + case Multiple: { + LOGI("Multiple root implementations found.\n"); + + break; + } + case KernelSU: { + LOGI("KernelSU root implementation found.\n"); + + break; + } + case APatch: { + LOGI("APatch root implementation found.\n"); + + break; + } + case Magisk: { + if (state_magisk.variant == 0) { + LOGI("Magisk Official root implementation found.\n"); + } else { + LOGI("Magisk Kitsune root implementation found.\n"); + } + + break; + } + } +} + +void get_impl(struct root_impl *uimpl) { + uimpl->impl = impl.impl; + uimpl->variant = impl.variant; +} + +bool uid_granted_root(uid_t uid) { + switch (impl.impl) { + case KernelSU: { + return ksu_uid_granted_root(uid); + } + case APatch: { + return apatch_uid_granted_root(uid); + } + case Magisk: { + return magisk_uid_granted_root(uid); + } + default: { + return false; + } + } +} + +bool uid_should_umount(uid_t uid) { + switch (impl.impl) { + case KernelSU: { + return ksu_uid_should_umount(uid); + } + case APatch: { + return apatch_uid_should_umount(uid); + } + case Magisk: { + return magisk_uid_should_umount(uid); + } + default: { + return false; + } + } +} + +bool uid_is_manager(uid_t uid) { + switch (impl.impl) { + case KernelSU: { + return ksu_uid_is_manager(uid); + } + case APatch: { + return apatch_uid_is_manager(uid); + } + case Magisk: { + return magisk_uid_is_manager(uid); + } + default: { + return false; + } + } +} diff --git a/zygiskd/src/root_impl/common.h b/zygiskd/src/root_impl/common.h new file mode 100644 index 00000000..dedb796c --- /dev/null +++ b/zygiskd/src/root_impl/common.h @@ -0,0 +1,38 @@ +#ifndef COMMON_H +#define COMMON_H + +#include + +#include "../constants.h" + +enum root_impls { + None, + Multiple, + KernelSU, + APatch, + Magisk +}; + +struct root_impl_state { + enum RootImplState state; + uint8_t variant; +}; + +struct root_impl { + enum root_impls impl; + uint8_t variant; +}; + +#define LONGEST_ROOT_IMPL_NAME sizeof("Magisk Official") + +void root_impls_setup(void); + +void get_impl(struct root_impl *uimpl); + +bool uid_granted_root(uid_t uid); + +bool uid_should_umount(uid_t uid); + +bool uid_is_manager(uid_t uid); + +#endif /* COMMON_H */ diff --git a/zygiskd/src/root_impl/kernelsu.c b/zygiskd/src/root_impl/kernelsu.c new file mode 100644 index 00000000..775b1c6b --- /dev/null +++ b/zygiskd/src/root_impl/kernelsu.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +#include "../constants.h" +#include "../utils.h" +#include "common.h" + +#include "kernelsu.h" + +/* INFO: It would be presumed it is a unsigned int, + so we need to cast it to signed int to + avoid any potential UB. +*/ +#define KERNEL_SU_OPTION 0xdeadbeef + +#define CMD_GET_VERSION 2 +#define CMD_UID_GRANTED_ROOT 12 +#define CMD_UID_SHOULD_UMOUNT 13 + +void ksu_get_existence(struct root_impl_state *state) { + int version = 0; + prctl((signed int)KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, 0); + + if (version == 0) state->state = Abnormal; + else if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) { + /* INFO: Some custom kernels for custom ROMs have pre-installed KernelSU. + Some users don't want to use KernelSU, but, for example, Magisk. + This if allows this to happen, as it checks if "ksud" exists, + which in case it doesn't, it won't be considered as supported. */ + struct stat s; + if (stat("/data/adb/ksud", &s) == -1) { + if (errno != ENOENT) { + LOGE("Failed to stat KSU daemon: %s\n", strerror(errno)); + } + errno = 0; + state->state = Abnormal; + + return; + } + + state->state = Supported; + } + else if (version >= 1 && version <= MIN_KSU_VERSION - 1) state->state = TooOld; + else state->state = Abnormal; +} + +bool ksu_uid_granted_root(uid_t uid) { + uint32_t result = 0; + bool granted = false; + prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &granted, &result); + + if (result != KERNEL_SU_OPTION) return false; + + return granted; +} + +bool ksu_uid_should_umount(uid_t uid) { + uint32_t result = 0; + bool umount = false; + prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &umount, &result); + + if (result != KERNEL_SU_OPTION) return false; + + return umount; +} + +bool ksu_uid_is_manager(uid_t uid) { + struct stat s; + if (stat("/data/user_de/0/me.weishu.kernelsu", &s) == -1) { + if (errno != ENOENT) { + LOGE("Failed to stat KSU manager data directory: %s\n", strerror(errno)); + } + errno = 0; + + return false; + } + + return s.st_uid == uid; +} diff --git a/zygiskd/src/root_impl/kernelsu.h b/zygiskd/src/root_impl/kernelsu.h new file mode 100644 index 00000000..96a4c721 --- /dev/null +++ b/zygiskd/src/root_impl/kernelsu.h @@ -0,0 +1,14 @@ +#ifndef KERNELSU_H +#define KERNELSU_H + +#include "../constants.h" + +void ksu_get_existence(struct root_impl_state *state); + +bool ksu_uid_granted_root(uid_t uid); + +bool ksu_uid_should_umount(uid_t uid); + +bool ksu_uid_is_manager(uid_t uid); + +#endif diff --git a/zygiskd/src/root_impl/kernelsu.rs b/zygiskd/src/root_impl/kernelsu.rs deleted file mode 100644 index da7177ee..00000000 --- a/zygiskd/src/root_impl/kernelsu.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::constants::{MAX_KSU_VERSION, MIN_KSU_VERSION}; - -const KERNEL_SU_OPTION: u32 = 0xdeadbeefu32; - -const CMD_GET_VERSION: usize = 2; -const CMD_UID_GRANTED_ROOT: usize = 12; -const CMD_UID_SHOULD_UMOUNT: usize = 13; - -pub enum Version { - Supported, - TooOld, - Abnormal, -} - -pub fn get_kernel_su() -> Option { - let mut version = 0; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_GET_VERSION, - &mut version as *mut i32, - 0, - 0, - ) - }; - const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1; - match version { - 0 => None, - MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported), - 1..=MAX_OLD_VERSION => Some(Version::TooOld), - _ => Some(Version::Abnormal), - } -} - -pub fn uid_granted_root(uid: i32) -> bool { - let mut result: u32 = 0; - let mut granted = false; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_UID_GRANTED_ROOT, - uid, - &mut granted as *mut bool, - &mut result as *mut u32, - ) - }; - if result != KERNEL_SU_OPTION { - log::warn!("uid_granted_root failed"); - } - granted -} - -pub fn uid_should_umount(uid: i32) -> bool { - let mut result: u32 = 0; - let mut umount = false; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_UID_SHOULD_UMOUNT, - uid, - &mut umount as *mut bool, - &mut result as *mut u32, - ) - }; - if result != KERNEL_SU_OPTION { - log::warn!("uid_granted_root failed"); - } - umount -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.weishu.kernelsu") { - return s.st_uid == uid as u32; - } - false -} diff --git a/zygiskd/src/root_impl/magisk.c b/zygiskd/src/root_impl/magisk.c new file mode 100644 index 00000000..298dc551 --- /dev/null +++ b/zygiskd/src/root_impl/magisk.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../constants.h" +#include "../utils.h" +#include "common.h" + +#include "magisk.h" + +char *supported_variants[] = { + "kitsune" +}; + +char *magisk_managers[] = { + "com.topjohnwu.magisk", + "io.github.huskydg.magisk" +}; + +#define SBIN_MAGISK lp_select("/sbin/magisk32", "/sbin/magisk64") +#define DEBUG_RAMDISK_MAGISK lp_select("/debug_ramdisk/magisk32", "/debug_ramdisk/magisk64") +#define BITLESS_DEBUG_RAMDISK_MAGISK "/debug_ramdisk/magisk" + +enum magisk_variants variant = Official; +/* INFO: Longest path */ +static char path_to_magisk[sizeof(DEBUG_RAMDISK_MAGISK)]; + +void magisk_get_existence(struct root_impl_state *state) { + struct stat s; + if (stat(SBIN_MAGISK, &s) != 0) { + if (errno != ENOENT) { + LOGE("Failed to stat Magisk /sbin/magisk binary: %s\n", strerror(errno)); + } + errno = 0; + + if (stat(DEBUG_RAMDISK_MAGISK, &s) != 0) { + if (errno != ENOENT) { + LOGE("Failed to stat Magisk %s binary: %s\n", DEBUG_RAMDISK_MAGISK, strerror(errno)); + } + errno = 0; + + if (stat(BITLESS_DEBUG_RAMDISK_MAGISK, &s) != 0) { + if (errno != ENOENT) { + LOGE("Failed to stat Magisk /debug_ramdisk/magisk binary: %s\n", strerror(errno)); + } + errno = 0; + + state->state = Inexistent; + + return; + } + + /* INFO: /debug_ramdisk/magisk64 (or 32) doesn't exist but /debug_ramdisk/magisk does */ + strcpy(path_to_magisk, BITLESS_DEBUG_RAMDISK_MAGISK); + } else { + /* INFO: /sbin/magisk doesn't exist but /debug_ramdisk/magisk does */ + strcpy(path_to_magisk, DEBUG_RAMDISK_MAGISK); + } + } else { + /* INFO: /sbin/magisk exists */ + strcpy(path_to_magisk, SBIN_MAGISK); + } + + char *argv[] = { "magisk", "-v", NULL }; + + char magisk_info[128]; + if (!exec_command(magisk_info, sizeof(magisk_info), (const char *)path_to_magisk, argv)) { + LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); + errno = 0; + + state->state = Abnormal; + + return; + } + + state->variant = (uint8_t)Official; + + for (unsigned long i = 0; i < sizeof(supported_variants) / sizeof(supported_variants[0]); i++) { + if (strstr(magisk_info, supported_variants[i])) { + variant = (enum magisk_variants)(i + 1); + state->variant = (uint8_t)variant; + + break; + } + } + + argv[1] = "-V"; + + char magisk_version[32]; + if (!exec_command(magisk_version, sizeof(magisk_version), (const char *)path_to_magisk, argv)) { + LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); + errno = 0; + + state->state = Abnormal; + + return; + } + + if (atoi(magisk_version) >= MIN_MAGISK_VERSION) state->state = Supported; + else state->state = TooOld; +} + +bool magisk_uid_granted_root(uid_t uid) { + char sqlite_cmd[256]; + snprintf(sqlite_cmd, sizeof(sqlite_cmd), "select 1 from policies where uid=%d and policy=2 limit 1", uid); + + char *const argv[] = { "magisk", "--sqlite", sqlite_cmd, NULL }; + + char result[32]; + if (!exec_command(result, sizeof(result), (const char *)path_to_magisk, argv)) { + LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); + errno = 0; + + return false; + } + + return result[0] != '\0'; +} + +bool magisk_uid_should_umount(uid_t uid) { + char uid_str[16]; + snprintf(uid_str, sizeof(uid_str), "%d", uid); + + char *const argv_pm[] = { "pm", "list", "packages", "--uid", uid_str, NULL }; + + char result[256]; + if (!exec_command(result, sizeof(result), "/system/bin/pm", argv_pm)) { + LOGE("Failed to execute pm binary: %s\n", strerror(errno)); + errno = 0; + + /* INFO: It's better if we do NOT umount than the opposite */ + return false; + } + + if (result[0] == '\0') { + LOGE("Failed to get package name from UID %d\n", uid); + + return false; + } + + char *package_name = strtok(result + strlen("package:"), " "); + + char sqlite_cmd[256]; + snprintf(sqlite_cmd, sizeof(sqlite_cmd), "select 1 from denylist where package_name=\"%s\" limit 1", package_name); + + char *const argv[] = { "magisk", "--sqlite", sqlite_cmd, NULL }; + + if (!exec_command(result, sizeof(result), (const char *)path_to_magisk, argv)) { + LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); + errno = 0; + + return false; + } + + return result[0] != '\0'; +} + +bool magisk_uid_is_manager(uid_t uid) { + char *const argv[] = { "magisk", "--sqlite", "select value from strings where key=\"requester\" limit 1", NULL }; + + char output[128]; + if (!exec_command(output, sizeof(output), (const char *)path_to_magisk, argv)) { + LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); + errno = 0; + + return false; + } + + char stat_path[PATH_MAX]; + if (output[0] == '\0') + snprintf(stat_path, sizeof(stat_path), "/data/user_de/0/%s", magisk_managers[(int)variant]); + else + snprintf(stat_path, sizeof(stat_path), "/data/user_de/0/%s", output + strlen("value=")); + + struct stat s; + if (stat(stat_path, &s) == -1) { + LOGE("Failed to stat %s: %s\n", stat_path, strerror(errno)); + errno = 0; + + return false; + } + + return s.st_uid == uid; +} diff --git a/zygiskd/src/root_impl/magisk.h b/zygiskd/src/root_impl/magisk.h new file mode 100644 index 00000000..0d47646e --- /dev/null +++ b/zygiskd/src/root_impl/magisk.h @@ -0,0 +1,19 @@ +#ifndef MAGISK_H +#define MAGISK_H + +#include "../constants.h" + +enum magisk_variants { + Official, + Kitsune +}; + +void magisk_get_existence(struct root_impl_state *state); + +bool magisk_uid_granted_root(uid_t uid); + +bool magisk_uid_should_umount(uid_t uid); + +bool magisk_uid_is_manager(uid_t uid); + +#endif diff --git a/zygiskd/src/root_impl/magisk.rs b/zygiskd/src/root_impl/magisk.rs deleted file mode 100644 index 612a03a8..00000000 --- a/zygiskd/src/root_impl/magisk.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fs; -use std::os::android::fs::MetadataExt; -use crate::constants::MIN_MAGISK_VERSION; -use std::process::{Command, Stdio}; -use log::info; -use crate::utils::LateInit; - -const MAGISK_OFFICIAL: &str = "com.topjohnwu.magisk"; -const MAGISK_THIRD_PARTIES: &[(&str, &str)] = &[ - ("alpha", "io.github.vvb2060.magisk"), - ("kitsune", "io.github.huskydg.magisk"), -]; - -pub enum Version { - Supported, - TooOld, -} - -static VARIANT: LateInit<&str> = LateInit::new(); - -pub fn get_magisk() -> Option { - if !VARIANT.initiated() { - Command::new("magisk") - .arg("-v") - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|version| { - let third_party = MAGISK_THIRD_PARTIES.iter().find_map(|v| { - version.contains(v.0).then_some(v.1) - }); - VARIANT.init(third_party.unwrap_or(MAGISK_OFFICIAL)); - info!("Magisk variant: {}", *VARIANT); - }); - } - Command::new("magisk") - .arg("-V") - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .and_then(|output| output.trim().parse::().ok()) - .map(|version| { - if version >= MIN_MAGISK_VERSION { - Version::Supported - } else { - Version::TooOld - } - }) -} - -pub fn uid_granted_root(uid: i32) -> bool { - Command::new("magisk") - .arg("--sqlite") - .arg(format!( - "select 1 from policies where uid={uid} and policy=2 limit 1" - )) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.is_empty()) - == Some(false) -} - -pub fn uid_should_umount(uid: i32) -> bool { - let output = Command::new("pm") - .args(["list", "packages", "--uid", &uid.to_string()]) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()); - let line = match output { - Some(line) => line, - None => return false, - }; - let pkg = line - .strip_prefix("package:") - .and_then(|line| line.split(' ').next()); - let pkg = match pkg { - Some(pkg) => pkg, - None => return false, - }; - Command::new("magisk") - .arg("--sqlite") - .arg(format!( - "select 1 from denylist where package_name=\"{pkg}\" limit 1" - )) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.is_empty()) - == Some(false) -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - let output = Command::new("magisk") - .arg("--sqlite") - .arg(format!("select value from strings where key=\"requester\" limit 1")) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.trim().to_string()); - if let Some(output) = output { - if let Some(manager) = output.strip_prefix("value=") { - return fs::metadata(format!("/data/user_de/0/{}", manager)) - .map(|s| s.st_uid() == uid as u32) - .unwrap_or(false); - } - } - fs::metadata(format!("/data/user_de/0/{}", *VARIANT)) - .map(|s| s.st_uid() == uid as u32) - .unwrap_or(false) -} diff --git a/zygiskd/src/root_impl/mod.rs b/zygiskd/src/root_impl/mod.rs deleted file mode 100644 index 7ff6ff1a..00000000 --- a/zygiskd/src/root_impl/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::ptr::addr_of; - -mod kernelsu; -mod magisk; -mod apatch; - -#[derive(Debug)] -pub enum RootImpl { - None, - TooOld, - Abnormal, - Multiple, - KernelSU, - Magisk, - APatch, -} - -static mut ROOT_IMPL: RootImpl = RootImpl::None; - -pub fn setup() { - let apatch_version = apatch::get_apatch(); - let ksu_version = kernelsu::get_kernel_su(); - let magisk_version = magisk::get_magisk(); - - let impl_ = match (apatch_version, ksu_version, magisk_version) { - (None, None, None) => RootImpl::None, - (Some(_), Some(_), Some(_)) => RootImpl::Multiple, - (Some(apatch_version),None, None) => match apatch_version { - apatch::Version::Supported => RootImpl::APatch, - apatch::Version::TooOld => RootImpl::TooOld, - apatch::Version::Abnormal => RootImpl::Abnormal, - }, - (None,Some(ksu_version), None) => match ksu_version { - kernelsu::Version::Supported => RootImpl::KernelSU, - kernelsu::Version::TooOld => RootImpl::TooOld, - kernelsu::Version::Abnormal => RootImpl::Abnormal, - }, - (None, None, Some(magisk_version)) => match magisk_version { - magisk::Version::Supported => RootImpl::Magisk, - magisk::Version::TooOld => RootImpl::TooOld, - }, - _ => RootImpl::None, - }; - unsafe { - ROOT_IMPL = impl_; - } -} - -pub fn get_impl() -> &'static RootImpl { - unsafe { &*addr_of!(ROOT_IMPL) } -} - -pub fn uid_granted_root(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_granted_root(uid), - RootImpl::Magisk => magisk::uid_granted_root(uid), - RootImpl::APatch => apatch::uid_granted_root(uid), - _ => panic!("uid_granted_root: unknown root impl {:?}", get_impl()), - } -} - -pub fn uid_should_umount(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_should_umount(uid), - RootImpl::Magisk => magisk::uid_should_umount(uid), - RootImpl::APatch => apatch::uid_should_umount(uid), - _ => panic!("uid_should_umount: unknown root impl {:?}", get_impl()), - } -} - -pub fn uid_is_manager(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_is_manager(uid), - RootImpl::Magisk => magisk::uid_is_manager(uid), - RootImpl::APatch => apatch::uid_is_manager(uid), - _ => panic!("uid_is_manager: unknown root impl {:?}", get_impl()), - } -} diff --git a/zygiskd/src/utils.c b/zygiskd/src/utils.c new file mode 100644 index 00000000..88314b96 --- /dev/null +++ b/zygiskd/src/utils.c @@ -0,0 +1,458 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "utils.h" +#include "root_impl/common.h" + +bool switch_mount_namespace(pid_t pid) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); + + int nsfd = open(path, O_RDONLY | O_CLOEXEC); + if (nsfd == -1) { + LOGE("Failed to open nsfd: %s\n", strerror(errno)); + + return false; + } + + if (setns(nsfd, CLONE_NEWNS) == -1) { + LOGE("Failed to setns: %s\n", strerror(errno)); + + close(nsfd); + + return false; + } + + close(nsfd); + + return true; +} + +int __system_property_get(const char *, char *); + +void get_property(const char *restrict name, char *restrict output) { + __system_property_get(name, output); +} + +void set_socket_create_context(const char *restrict context) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "/proc/thread-self/attr/sockcreate"); + + FILE *sockcreate = fopen(path, "w"); + if (sockcreate == NULL) { + LOGE("Failed to open /proc/thread-self/attr/sockcreate: %s Now trying to via gettid().\n", strerror(errno)); + + goto fail; + } + + if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) { + LOGE("Failed to write to /proc/thread-self/attr/sockcreate: %s Now trying to via gettid().\n", strerror(errno)); + + fclose(sockcreate); + + goto fail; + } + + fclose(sockcreate); + + return; + + fail: + snprintf(path, PATH_MAX, "/proc/self/task/%d/attr/sockcreate", gettid()); + + sockcreate = fopen(path, "w"); + if (sockcreate == NULL) { + LOGE("Failed to open %s: %s\n", path, strerror(errno)); + + return; + } + + if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) { + LOGE("Failed to write to %s: %s\n", path, strerror(errno)); + + return; + } + + fclose(sockcreate); +} + +static void get_current_attr(char *restrict output, size_t size) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "/proc/self/attr/current"); + + FILE *current = fopen(path, "r"); + if (current == NULL) { + LOGE("fopen: %s\n", strerror(errno)); + + return; + } + + if (fread(output, 1, size, current) == 0) { + LOGE("fread: %s\n", strerror(errno)); + + return; + } + + fclose(current); +} + +void unix_datagram_sendto(const char *restrict path, void *restrict buf, size_t len) { + char current_attr[PATH_MAX]; + get_current_attr(current_attr, sizeof(current_attr)); + + set_socket_create_context(current_attr); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + int socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (socket_fd == -1) { + LOGE("socket: %s\n", strerror(errno)); + + return; + } + + if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOGE("connect: %s\n", strerror(errno)); + + return; + } + + if (sendto(socket_fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOGE("sendto: %s\n", strerror(errno)); + + return; + } + + set_socket_create_context("u:r:zygote:s0"); + + close(socket_fd); +} + +int chcon(const char *restrict path, const char *context) { + char command[PATH_MAX]; + snprintf(command, PATH_MAX, "chcon %s %s", context, path); + + return system(command); +} + +int unix_listener_from_path(char *restrict path) { + if (remove(path) == -1 && errno != ENOENT) { + LOGE("remove: %s\n", strerror(errno)); + + return -1; + } + + int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_fd == -1) { + LOGE("socket: %s\n", strerror(errno)); + + return -1; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX + }; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + LOGE("bind: %s\n", strerror(errno)); + + return -1; + } + + if (listen(socket_fd, 2) == -1) { + LOGE("listen: %s\n", strerror(errno)); + + return -1; + } + + if (chcon(path, "u:object_r:magisk_file:s0") == -1) { + LOGE("chcon: %s\n", strerror(errno)); + + return -1; + } + + return socket_fd; +} + +ssize_t write_fd(int fd, int sendfd) { + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + char buf[1] = { 0 }; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf) + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + memcpy(CMSG_DATA(cmsg), &sendfd, sizeof(int)); + + ssize_t ret = sendmsg(fd, &msg, 0); + if (ret == -1) { + LOGE("sendmsg: %s\n", strerror(errno)); + + return -1; + } + + return ret; +} + +int read_fd(int fd) { + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + char buf[1] = { 0 }; + + struct iovec iov = { + .iov_base = buf, + .iov_len = 1 + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf) + }; + + ssize_t ret = recvmsg(fd, &msg, 0); + if (ret == -1) { + LOGE("recvmsg: %s\n", strerror(errno)); + + return -1; + } + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL) { + LOGE("CMSG_FIRSTHDR: %s\n", strerror(errno)); + + return -1; + } + + int sendfd; + memcpy(&sendfd, CMSG_DATA(cmsg), sizeof(int)); + + return sendfd; +} + +#define write_func(type) \ + ssize_t write_## type(int fd, type val) { \ + return write(fd, &val, sizeof(type)); \ + } + +#define read_func(type) \ + ssize_t read_## type(int fd, type *val) { \ + return read(fd, val, sizeof(type)); \ + } + +write_func(int) +read_func(int) + +write_func(size_t) +read_func(size_t) + +write_func(uint32_t) +read_func(uint32_t) + +write_func(uint8_t) +read_func(uint8_t) + +ssize_t write_string(int fd, const char *restrict str) { + size_t len[1]; + len[0] = strlen(str); + + ssize_t written_bytes = write(fd, &len, sizeof(size_t)); + if (written_bytes != sizeof(size_t)) { + LOGE("Failed to write string length: Not all bytes were written (%zd != %zu).\n", written_bytes, sizeof(size_t)); + + return -1; + } + + written_bytes = write(fd, str, len[0]); + if ((size_t)written_bytes != len[0]) { + LOGE("Failed to write string: Not all bytes were written.\n"); + + return -1; + } + + return written_bytes; +} + +ssize_t read_string(int fd, char *restrict str, size_t len) { + size_t str_len_buf[1]; + + ssize_t read_bytes = read(fd, &str_len_buf, sizeof(size_t)); + if (read_bytes != (ssize_t)sizeof(size_t)) { + LOGE("Failed to read string length: Not all bytes were read (%zd != %zu).\n", read_bytes, sizeof(size_t)); + + return -1; + } + + size_t str_len = str_len_buf[0]; + + if (str_len > len) { + LOGE("Failed to read string: Buffer is too small (%zu > %zu).\n", str_len, len); + + return -1; + } + + read_bytes = read(fd, str, str_len); + if (read_bytes != (ssize_t)str_len) { + LOGE("Failed to read string: Promised bytes doesn't exist (%zd != %zu).\n", read_bytes, str_len); + + return -1; + } + + return read_bytes; +} + +/* INFO: Cannot use restrict here as execv does not have restrict */ +bool exec_command(char *restrict buf, size_t len, const char *restrict file, char *const argv[]) { + int link[2]; + pid_t pid; + + if (pipe(link) == -1) { + LOGE("pipe: %s\n", strerror(errno)); + + return false; + } + + if ((pid = fork()) == -1) { + LOGE("fork: %s\n", strerror(errno)); + + close(link[0]); + close(link[1]); + + return false; + } + + if (pid == 0) { + dup2(link[1], STDOUT_FILENO); + close(link[0]); + close(link[1]); + + execv(file, argv); + + LOGE("execv failed: %s\n", strerror(errno)); + _exit(1); + } else { + close(link[1]); + + int nbytes = read(link[0], buf, len); + if (nbytes > 0) buf[nbytes - 1] = '\0'; + /* INFO: If something went wrong, at least we must ensure it is NULL-terminated */ + else buf[0] = '\0'; + + wait(NULL); + + close(link[0]); + } + + return true; +} + +bool check_unix_socket(int fd, bool block) { + struct pollfd pfd = { + .fd = fd, + .events = POLLIN, + .revents = 0 + }; + + int timeout = block ? -1 : 0; + poll(&pfd, 1, timeout); + + return pfd.revents & ~POLLIN ? false : true; +} + +/* INFO: Cannot use restrict here as execv does not have restrict */ +int non_blocking_execv(const char *restrict file, char *const argv[]) { + int link[2]; + pid_t pid; + + if (pipe(link) == -1) { + LOGE("pipe: %s\n", strerror(errno)); + + return -1; + } + + if ((pid = fork()) == -1) { + LOGE("fork: %s\n", strerror(errno)); + + return -1; + } + + if (pid == 0) { + dup2(link[1], STDOUT_FILENO); + close(link[0]); + close(link[1]); + + execv(file, argv); + } else { + close(link[1]); + + return link[0]; + } + + return -1; +} + +void stringify_root_impl_name(struct root_impl impl, char *restrict output) { + switch (impl.impl) { + case None: { + strcpy(output, "None"); + + break; + } + case Multiple: { + strcpy(output, "Multiple"); + + break; + } + case KernelSU: { + strcpy(output, "KernelSU"); + + break; + } + case APatch: { + strcpy(output, "APatch"); + + break; + } + case Magisk: { + if (impl.variant == 0) { + strcpy(output, "Magisk Official"); + } else { + strcpy(output, "Magisk Kitsune"); + } + + break; + } + } +} diff --git a/zygiskd/src/utils.h b/zygiskd/src/utils.h new file mode 100644 index 00000000..c7f7a99c --- /dev/null +++ b/zygiskd/src/utils.h @@ -0,0 +1,107 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +#include "constants.h" +#include "root_impl/common.h" + +#define CONCAT_(x,y) x##y +#define CONCAT(x,y) CONCAT_(x,y) + +#define LOGI(...) \ + __android_log_print(ANDROID_LOG_INFO, lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ + printf(__VA_ARGS__); + +#define LOGE(...) \ + __android_log_print(ANDROID_LOG_ERROR , lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ + printf(__VA_ARGS__); + +#define ASSURE_SIZE_WRITE(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return; \ + } + +#define ASSURE_SIZE_READ(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return; \ + } + +#define ASSURE_SIZE_WRITE_BREAK(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + break; \ + } + +#define ASSURE_SIZE_READ_BREAK(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + break; \ + } + +#define ASSURE_SIZE_WRITE_WR(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return -1; \ + } + +#define ASSURE_SIZE_READ_WR(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return -1; \ + } + +#define write_func_def(type) \ + ssize_t write_## type(int fd, type val) + +#define read_func_def(type) \ + ssize_t read_## type(int fd, type *val) + +bool switch_mount_namespace(pid_t pid); + +void get_property(const char *name, char *restrict output); + +void set_socket_create_context(const char *restrict context); + +void unix_datagram_sendto(const char *restrict path, void *restrict buf, size_t len); + +int chcon(const char *path, const char *restrict context); + +int unix_listener_from_path(char *path); + +ssize_t write_fd(int fd, int sendfd); +int read_fd(int fd); + +write_func_def(int); +read_func_def(int); + +write_func_def(size_t); +read_func_def(size_t); + +write_func_def(uint32_t); +read_func_def(uint32_t); + +write_func_def(uint8_t); +read_func_def(uint8_t); + +ssize_t write_string(int fd, const char *restrict str); + +ssize_t read_string(int fd, char *restrict str, size_t len); + +bool exec_command(char *restrict buf, size_t len, const char *restrict file, char *const argv[]); + +bool check_unix_socket(int fd, bool block); + +int non_blocking_execv(const char *restrict file, char *const argv[]); + +void stringify_root_impl_name(struct root_impl impl, char *restrict output); + +#endif /* UTILS_H */ diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs deleted file mode 100644 index 772b8168..00000000 --- a/zygiskd/src/utils.rs +++ /dev/null @@ -1,264 +0,0 @@ -use anyhow::Result; -use rustix::net::{ - bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix, - SocketType, -}; -use rustix::path::Arg; -use rustix::thread::gettid; -use std::ffi::{c_char, c_void, CStr, CString}; -use std::os::fd::{AsFd, AsRawFd}; -use std::os::unix::net::{UnixListener}; -use std::process::Command; -use std::sync::OnceLock; -use std::{ - fs, - io::{Read, Write}, - os::unix::net::UnixStream, -}; - -#[cfg(target_pointer_width = "64")] -#[macro_export] -macro_rules! lp_select { - ($lp32:expr, $lp64:expr) => { - $lp64 - }; -} -#[cfg(target_pointer_width = "32")] -#[macro_export] -macro_rules! lp_select { - ($lp32:expr, $lp64:expr) => { - $lp32 - }; -} - -#[cfg(debug_assertions)] -#[macro_export] -macro_rules! debug_select { - ($debug:expr, $release:expr) => { - $debug - }; -} -#[cfg(not(debug_assertions))] -#[macro_export] -macro_rules! debug_select { - ($debug:expr, $release:expr) => { - $release - }; -} - -pub struct LateInit { - cell: OnceLock, -} - -impl LateInit { - pub const fn new() -> Self { - LateInit { - cell: OnceLock::new(), - } - } - - pub fn init(&self, value: T) { - assert!(self.cell.set(value).is_ok()) - } - - pub fn initiated(&self) -> bool { - self.cell.get().is_some() - } -} - -impl std::ops::Deref for LateInit { - type Target = T; - fn deref(&self) -> &T { - self.cell.get().unwrap() - } -} - -pub fn set_socket_create_context(context: &str) -> Result<()> { - let path = "/proc/thread-self/attr/sockcreate"; - match fs::write(path, context) { - Ok(_) => Ok(()), - Err(_) => { - let path = format!( - "/proc/self/task/{}/attr/sockcreate", - gettid().as_raw_nonzero() - ); - fs::write(path, context)?; - Ok(()) - } - } -} - -pub fn get_current_attr() -> Result { - let s = fs::read("/proc/self/attr/current")?; - Ok(s.to_string_lossy().to_string()) -} - -pub fn chcon(path: &str, context: &str) -> Result<()> { - Command::new("chcon").arg(context).arg(path).status()?; - Ok(()) -} - -pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> { - let tag = CString::new(tag)?; - let message = CString::new(message)?; - unsafe { - __android_log_print(level, tag.as_ptr(), message.as_ptr()); - } - Ok(()) -} - -pub fn get_property(name: &str) -> Result { - let name = CString::new(name)?; - let mut buf = vec![0u8; 92]; - let prop = unsafe { - __system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char); - CStr::from_bytes_until_nul(&buf)? - }; - Ok(prop.to_string_lossy().to_string()) -} - -#[allow(dead_code)] -pub fn set_property(name: &str, value: &str) -> Result<()> { - let name = CString::new(name)?; - let value = CString::new(value)?; - unsafe { - __system_property_set(name.as_ptr(), value.as_ptr()); - } - Ok(()) -} - -#[allow(dead_code)] -pub fn wait_property(name: &str, serial: u32) -> Result { - let name = CString::new(name)?; - let info = unsafe { __system_property_find(name.as_ptr()) }; - let mut serial = serial; - unsafe { - __system_property_wait(info, serial, &mut serial, std::ptr::null()); - } - Ok(serial) -} - -#[allow(dead_code)] -pub fn get_property_serial(name: &str) -> Result { - let name = CString::new(name)?; - let info = unsafe { __system_property_find(name.as_ptr()) }; - Ok(unsafe { __system_property_serial(info) }) -} - -pub fn switch_mount_namespace(pid: i32) -> Result<()> { - let cwd = std::env::current_dir()?; - let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?; - rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?; - std::env::set_current_dir(cwd)?; - Ok(()) -} - -pub trait UnixStreamExt { - fn read_u8(&mut self) -> Result; - fn read_u32(&mut self) -> Result; - fn read_usize(&mut self) -> Result; - fn read_string(&mut self) -> Result; - fn write_u8(&mut self, value: u8) -> Result<()>; - fn write_u32(&mut self, value: u32) -> Result<()>; - fn write_usize(&mut self, value: usize) -> Result<()>; - fn write_string(&mut self, value: &str) -> Result<()>; -} - -impl UnixStreamExt for UnixStream { - fn read_u8(&mut self) -> Result { - let mut buf = [0u8; 1]; - self.read_exact(&mut buf)?; - Ok(buf[0]) - } - - fn read_u32(&mut self) -> Result { - let mut buf = [0u8; 4]; - self.read_exact(&mut buf)?; - Ok(u32::from_ne_bytes(buf)) - } - - fn read_usize(&mut self) -> Result { - let mut buf = [0u8; std::mem::size_of::()]; - self.read_exact(&mut buf)?; - Ok(usize::from_ne_bytes(buf)) - } - - fn read_string(&mut self) -> Result { - let len = self.read_usize()?; - let mut buf = vec![0u8; len]; - self.read_exact(&mut buf)?; - Ok(String::from_utf8(buf)?) - } - - fn write_u8(&mut self, value: u8) -> Result<()> { - self.write_all(&value.to_ne_bytes())?; - Ok(()) - } - - fn write_u32(&mut self, value: u32) -> Result<()> { - self.write_all(&value.to_ne_bytes())?; - Ok(()) - } - - fn write_usize(&mut self, value: usize) -> Result<()> { - self.write_all(&value.to_ne_bytes())?; - Ok(()) - } - - fn write_string(&mut self, value: &str) -> Result<()> { - self.write_usize(value.len())?; - self.write_all(value.as_bytes())?; - Ok(()) - } -} - -pub fn unix_listener_from_path(path: &str) -> Result { - let _ = fs::remove_file(path); - let addr = SocketAddrUnix::new(path)?; - let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?; - bind_unix(&socket, &addr)?; - listen(&socket, 2)?; - chcon(path, "u:object_r:magisk_file:s0")?; - Ok(UnixListener::from(socket)) -} - -pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> { - // FIXME: shall we set create context every time? - set_socket_create_context(get_current_attr()?.as_str())?; - let addr = SocketAddrUnix::new(path.as_bytes())?; - let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?; - connect_unix(&socket, &addr)?; - sendto_unix(socket, buf, SendFlags::empty(), &addr)?; - set_socket_create_context("u:r:zygote:s0")?; - Ok(()) -} - -pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool { - unsafe { - let mut pfd = libc::pollfd { - fd: stream.as_raw_fd(), - events: libc::POLLIN, - revents: 0, - }; - let timeout = if block { -1 } else { 0 }; - libc::poll(&mut pfd, 1, timeout); - if pfd.revents & !libc::POLLIN != 0 { - return false; - } - } - return true; -} - -extern "C" { - fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32; - fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32; - fn __system_property_set(name: *const c_char, value: *const c_char) -> u32; - fn __system_property_find(name: *const c_char) -> *const c_void; - fn __system_property_wait( - info: *const c_void, - old_serial: u32, - new_serial: *mut u32, - timeout: *const libc::timespec, - ) -> bool; - fn __system_property_serial(info: *const c_void) -> u32; -} diff --git a/zygiskd/src/zygiskd.c b/zygiskd/src/zygiskd.c new file mode 100644 index 00000000..93338aeb --- /dev/null +++ b/zygiskd/src/zygiskd.c @@ -0,0 +1,713 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "root_impl/common.h" +#include "constants.h" +#include "utils.h" + +struct Module { + char *name; + int lib_fd; + int companion; +}; + +struct Context { + struct Module *modules; + int len; +}; + +enum Architecture { + ARM32, + ARM64, + X86, + X86_64, +}; + +#define PATH_MODULES_DIR "/data/adb/modules" +#define TMP_PATH "/data/adb/rezygisk" +#define CONTROLLER_SOCKET TMP_PATH "/init_monitor" +#define PATH_CP_NAME TMP_PATH "/" lp_select("cp32.sock", "cp64.sock") +#define ZYGISKD_FILE PATH_MODULES_DIR "/zygisksu/bin/zygiskd" lp_select("32", "64") +#define ZYGISKD_PATH "/data/adb/modules/zygisksu/bin/zygiskd" lp_select("32", "64") + +static enum Architecture get_arch(void) { + char system_arch[32]; + get_property("ro.product.cpu.abi", system_arch); + + if (strstr(system_arch, "arm") != NULL) return lp_select(ARM32, ARM64); + if (strstr(system_arch, "x86") != NULL) return lp_select(X86, X86_64); + + LOGE("Unsupported system architecture: %s\n", system_arch); + exit(1); +} + +int create_library_fd(const char *restrict so_path) { + int so_fd = open(so_path, O_RDONLY); + if (so_fd == -1) { + LOGE("Failed opening so file: %s\n", strerror(errno)); + + return -1; + } + + off_t so_size = lseek(so_fd, 0, SEEK_END); + if (so_size == -1) { + LOGE("Failed getting so file size: %s\n", strerror(errno)); + + close(so_fd); + + return -1; + } + + if (lseek(so_fd, 0, SEEK_SET) == -1) { + LOGE("Failed seeking so file: %s\n", strerror(errno)); + + close(so_fd); + + return -1; + } + + /* INFO: This is required as older implementations of glibc may not + have the memfd_create function call, causing a crash. */ + int memfd = syscall(SYS_memfd_create, "jit-cache-zygisk", MFD_ALLOW_SEALING); + if (memfd == -1) { + LOGE("Failed creating memfd: %s\n", strerror(errno)); + + return -1; + } + + if (sendfile(memfd, so_fd, NULL, so_size) == -1) { + LOGE("Failed copying so file to memfd: %s\n", strerror(errno)); + + close(so_fd); + close(memfd); + + return -1; + } + + close(so_fd); + + if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) == -1) { + LOGE("Failed sealing memfd: %s\n", strerror(errno)); + + close(memfd); + + return -1; + } + + return memfd; +} + +/* WARNING: Dynamic memory based */ +static void load_modules(enum Architecture arch, struct Context *restrict context) { + context->len = 0; + context->modules = NULL; + + DIR *dir = opendir(PATH_MODULES_DIR); + if (dir == NULL) { + LOGE("Failed opening modules directory: %s.", PATH_MODULES_DIR); + + return; + } + + char arch_str[32]; + switch (arch) { + case ARM64: { strcpy(arch_str, "arm64-v8a"); break; } + case X86_64: { strcpy(arch_str, "x86_64"); break; } + case ARM32: { strcpy(arch_str, "armeabi-v7a"); break; } + case X86: { strcpy(arch_str, "x86"); break; } + } + + LOGI("Loading modules for architecture: %s\n", arch_str); + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) continue; /* INFO: Only directories */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, "zygisksu") == 0) continue; + + char *name = entry->d_name; + char so_path[PATH_MAX]; + snprintf(so_path, PATH_MAX, "/data/adb/modules/%s/zygisk/%s.so", name, arch_str); + + struct stat st; + if (stat(so_path, &st) == -1) { + errno = 0; + + continue; + } + + char disabled[PATH_MAX]; + snprintf(disabled, PATH_MAX, "/data/adb/modules/%s/disable", name); + + if (stat(disabled, &st) == -1) { + if (errno != ENOENT) { + LOGE("Failed checking if module `%s` is disabled: %s\n", name, strerror(errno)); + errno = 0; + + continue; + } + + errno = 0; + } else continue; + + int lib_fd = create_library_fd(so_path); + if (lib_fd == -1) { + LOGE("Failed loading module `%s`\n", name); + + continue; + } + + + context->modules = realloc(context->modules, ((context->len + 1) * sizeof(struct Module))); + if (context->modules == NULL) { + LOGE("Failed reallocating memory for modules.\n"); + + return; + } + + context->modules[context->len].name = strdup(name); + context->modules[context->len].lib_fd = lib_fd; + context->modules[context->len].companion = -1; + context->len++; + } +} + +static void free_modules(struct Context *restrict context) { + for (int i = 0; i < context->len; i++) { + free(context->modules[i].name); + if (context->modules[i].companion != -1) close(context->modules[i].companion); + } +} + +static int create_daemon_socket(void) { + set_socket_create_context("u:r:zygote:s0"); + + return unix_listener_from_path(PATH_CP_NAME); +} + +static int spawn_companion(char *restrict argv[], char *restrict name, int lib_fd) { + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) { + LOGE("Failed creating socket pair.\n"); + + return -1; + } + + int daemon_fd = sockets[0]; + int companion_fd = sockets[1]; + + pid_t pid = fork(); + if (pid < 0) { + LOGE("Failed forking companion: %s\n", strerror(errno)); + + close(companion_fd); + close(daemon_fd); + + exit(1); + } else if (pid > 0) { + close(companion_fd); + + int status = 0; + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + if (write_string(daemon_fd, name) == -1) { + LOGE("Failed writing module name.\n"); + + close(daemon_fd); + + return -1; + } + if (write_fd(daemon_fd, lib_fd) == -1) { + LOGE("Failed sending library fd.\n"); + + close(daemon_fd); + + return -1; + } + + uint8_t response = 0; + ssize_t ret = read_uint8_t(daemon_fd, &response); + if (ret <= 0) { + LOGE("Failed reading companion response.\n"); + + close(daemon_fd); + + return -1; + } + + switch (response) { + /* INFO: Even without any entry, we should still just deal with it */ + case 0: { return -2; } + case 1: { return daemon_fd; } + /* TODO: Should we be closing daemon socket here? (in non-0-and-1 case) */ + default: { + return -1; + } + } + /* TODO: Should we be closing daemon socket here? */ + } else { + LOGE("Exited with status %d\n", status); + + return -1; + } + /* INFO: if pid == 0: */ + } else { + /* INFO: There is no case where this will fail with a valid fd. */ + /* INFO: Remove FD_CLOEXEC flag to avoid closing upon exec */ + if (fcntl(companion_fd, F_SETFD, 0) == -1) { + LOGE("Failed removing FD_CLOEXEC flag: %s\n", strerror(errno)); + + close(companion_fd); + close(daemon_fd); + + exit(1); + } + } + + char *process = argv[0]; + char nice_name[256]; + char *last = strrchr(process, '/'); + if (last == NULL) { + snprintf(nice_name, sizeof(nice_name), "%s", process); + } else { + snprintf(nice_name, sizeof(nice_name), "%s", last + 1); + } + + char process_name[256]; + snprintf(process_name, sizeof(process_name), "%s-%s", nice_name, name); + + char companion_fd_str[32]; + snprintf(companion_fd_str, sizeof(companion_fd_str), "%d", companion_fd); + + char *eargv[] = { process_name, "companion", companion_fd_str, NULL }; + if (non_blocking_execv(ZYGISKD_PATH, eargv) == -1) { + LOGE("Failed executing companion: %s\n", strerror(errno)); + + close(companion_fd); + + exit(1); + } + + exit(0); +} + +struct __attribute__((__packed__)) MsgHead { + unsigned int cmd; + int length; + char data[0]; +}; + +/* WARNING: Dynamic memory based */ +void zygiskd_start(char *restrict argv[]) { + /* INFO: When implementation is None or Multiple, it won't set the values + for the context, causing it to have garbage values. In response + to that, "= { 0 }" is used to ensure that the values are clean. */ + struct Context context = { 0 }; + + struct root_impl impl; + get_impl(&impl); + if (impl.impl == None || impl.impl == Multiple) { + struct MsgHead *msg = NULL; + + if (impl.impl == None) { + msg = malloc(sizeof(struct MsgHead) + strlen("Unsupported environment: Unknown root implementation") + 1); + } else { + msg = malloc(sizeof(struct MsgHead) + strlen("Unsupported environment: Multiple root implementations found") + 1); + } + if (msg == NULL) { + LOGE("Failed allocating memory for message.\n"); + + return; + } + + msg->cmd = DAEMON_SET_ERROR_INFO; + if (impl.impl == None) { + msg->length = sprintf(msg->data, "Unsupported environment: Unknown root implementation"); + } else { + msg->length = sprintf(msg->data, "Unsupported environment: Multiple root implementations found"); + } + + unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, sizeof(struct MsgHead) + msg->length); + + free(msg); + } else { + enum Architecture arch = get_arch(); + load_modules(arch, &context); + + char *module_list = NULL; + size_t module_list_len = 0; + if (context.len == 0) { + module_list = strdup("None"); + module_list_len = strlen("None"); + } else { + for (int i = 0; i < context.len; i++) { + if (i != context.len - 1) { + module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + strlen(", ") + 1); + if (module_list == NULL) { + LOGE("Failed reallocating memory for module list.\n"); + + return; + } + + strcpy(module_list + module_list_len, context.modules[i].name); + + module_list_len += strlen(context.modules[i].name); + + strcpy(module_list + module_list_len, ", "); + + module_list_len += strlen(", "); + } else { + module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + 1); + if (module_list == NULL) { + LOGE("Failed reallocating memory for module list.\n"); + + return; + } + + strcpy(module_list + module_list_len, context.modules[i].name); + + module_list_len += strlen(context.modules[i].name); + } + } + } + + char impl_name[LONGEST_ROOT_IMPL_NAME]; + stringify_root_impl_name(impl, impl_name); + + size_t msg_length = strlen("Root: , Modules: ") + strlen(impl_name) + module_list_len + 1; + + struct MsgHead *msg = malloc(sizeof(struct MsgHead) + msg_length); + msg->length = snprintf(msg->data, msg_length, "Root: %s, Modules: %s", impl_name, module_list); + msg->cmd = DAEMON_SET_INFO; + + unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, sizeof(struct MsgHead) + msg->length); + + free(msg); + free(module_list); + } + + int socket_fd = create_daemon_socket(); + if (socket_fd == -1) { + LOGE("Failed creating daemon socket\n"); + + return; + } + + while (1) { + int client_fd = accept(socket_fd, NULL, NULL); + if (client_fd == -1) { + LOGE("accept: %s\n", strerror(errno)); + + return; + } + + uint8_t action8 = 0; + ssize_t len = read_uint8_t(client_fd, &action8); + if (len == -1) { + LOGE("read: %s\n", strerror(errno)); + + return; + } else if (len == 0) { + LOGI("Client disconnected\n"); + + return; + } + + enum DaemonSocketAction action = (enum DaemonSocketAction)action8; + + switch (action) { + case PingHeartbeat: { + enum DaemonSocketAction msgr = ZYGOTE_INJECTED; + unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction)); + + break; + } + case ZygoteRestart: { + for (int i = 0; i < context.len; i++) { + if (context.modules[i].companion != -1) { + close(context.modules[i].companion); + context.modules[i].companion = -1; + } + } + + break; + } + case SystemServerStarted: { + enum DaemonSocketAction msgr = SYSTEM_SERVER_STARTED; + unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction)); + + if (impl.impl == None || impl.impl == Multiple) { + LOGI("Unsupported environment detected. Exiting.\n"); + + close(client_fd); + close(socket_fd); + free_modules(&context); + + exit(1); + } + + break; + } + 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) - 1); + if (ret == -1) { + LOGE("Failed reading logcat tag.\n"); + + close(client_fd); + + break; + } + + tag[ret] = '\0'; + + /* INFO: Non-NULL terminated */ + char message[1024]; + 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", (int)ret, 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; + } else { + if (uid_granted_root(uid)) { + flags |= PROCESS_GRANTED_ROOT; + } + if (uid_should_umount(uid)) { + flags |= PROCESS_ON_DENYLIST; + } + } + + switch (impl.impl) { + case None: { break; } + case Multiple: { break; } + case KernelSU: { + flags |= PROCESS_ROOT_IS_KSU; + + break; + } + case APatch: { + flags |= PROCESS_ROOT_IS_APATCH; + + break; + } + case Magisk: { + flags |= PROCESS_ROOT_IS_MAGISK; + + break; + } + } + + ret = write_int(client_fd, flags); + ASSURE_SIZE_WRITE_BREAK("GetProcessFlags", "flags", ret, sizeof(flags)); + + break; + } + case GetInfo: { + uint32_t flags = 0; + + switch (impl.impl) { + case None: { break; } + case Multiple: { break; } + case KernelSU: { + flags |= PROCESS_ROOT_IS_KSU; + + break; + } + case APatch: { + flags |= PROCESS_ROOT_IS_APATCH; + + break; + } + case Magisk: { + flags |= PROCESS_ROOT_IS_MAGISK; + + break; + } + } + + ssize_t ret = write_size_t(client_fd, flags); + ASSURE_SIZE_WRITE_BREAK("GetInfo", "flags", ret, sizeof(flags)); + + uint32_t pid = getpid(); + ret = write_uint32_t(client_fd, pid); + ASSURE_SIZE_WRITE_BREAK("GetInfo", "pid", ret, sizeof(pid)); + + size_t modules_len = context.len; + ret = write_size_t(client_fd, modules_len); + ASSURE_SIZE_WRITE_BREAK("GetInfo", "modules_len", ret, sizeof(modules_len)); + + for (size_t i = 0; i < modules_len; i++) { + ret = write_string(client_fd, context.modules[i].name); + if (ret == -1) { + LOGE("Failed writing module name.\n"); + + break; + } + } + + break; + } + case ReadModules: { + size_t clen = context.len; + ssize_t ret = write_size_t(client_fd, clen); + ASSURE_SIZE_WRITE_BREAK("ReadModules", "len", ret, sizeof(clen)); + + for (size_t i = 0; i < clen; i++) { + if (write_string(client_fd, context.modules[i].name) == -1) { + LOGE("Failed writing module name.\n"); + + break; + } + if (write_fd(client_fd, context.modules[i].lib_fd) == -1) { + LOGE("Failed writing module fd.\n"); + + break; + } + } + + break; + } + case RequestCompanionSocket: { + size_t index = 0; + ssize_t ret = read_size_t(client_fd, &index); + ASSURE_SIZE_READ_BREAK("RequestCompanionSocket", "index", ret, sizeof(index)); + + struct Module *module = &context.modules[index]; + + if (module->companion != -1) { + LOGI(" - Polling companion for module \"%s\"\n", module->name); + + if (!check_unix_socket(module->companion, false)) { + LOGE(" - Poll companion for module \"%s\" crashed\n", module->name); + + close(module->companion); + module->companion = -1; + } + } + + if (module->companion == -1) { + module->companion = spawn_companion(argv, module->name, module->lib_fd); + + if (module->companion > 0) { + LOGI(" - Spawned companion for \"%s\"\n", module->name); + } else { + if (module->companion == -2) { + LOGE(" - No companion spawned for \"%s\" because it has no entry.\n", module->name); + } else { + LOGE(" - Failed to spawn companion for \"%s\": %s\n", module->name, strerror(errno)); + } + } + } + + /* + INFO: Companion already exists or was created. In any way, + it should be in the while loop to receive fds now, + so just sending the file descriptor of the client is + safe. + */ + if (module->companion != -1) { + LOGI(" - Sending companion fd socket of module \"%s\"\n", module->name); + + if (write_fd(module->companion, client_fd) == -1) { + LOGE(" - Failed to send companion fd socket of module \"%s\"\n", module->name); + + ret = write_uint8_t(client_fd, 0); + ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(int)); + + close(module->companion); + module->companion = -1; + + /* INFO: RequestCompanionSocket by defailt doesn't close the client_fd */ + close(client_fd); + } + } else { + ret = write_uint8_t(client_fd, 0); + ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(int)); + + /* INFO: RequestCompanionSocket by defailt doesn't close the client_fd */ + close(client_fd); + } + + break; + } + case GetModuleDir: { + size_t index = 0; + ssize_t ret = read_size_t(client_fd, &index); + ASSURE_SIZE_READ_BREAK("GetModuleDir", "index", ret, sizeof(index)); + + char module_dir[PATH_MAX]; + snprintf(module_dir, PATH_MAX, "%s/%s", PATH_MODULES_DIR, context.modules[index].name); + + int fd = open(module_dir, O_RDONLY); + if (fd == -1) { + LOGE("Failed opening module directory \"%s\": %s\n", module_dir, strerror(errno)); + + break; + } + + struct stat st; + if (fstat(fd, &st) == -1) { + LOGE("Failed getting module directory \"%s\" stats: %s\n", module_dir, strerror(errno)); + + close(fd); + + break; + } + + if (write_fd(client_fd, fd) == -1) { + LOGE("Failed sending module directory \"%s\" fd: %s\n", module_dir, strerror(errno)); + + close(fd); + + break; + } + + break; + } + } + + if (action != RequestCompanionSocket && action != RequestLogcatFd) close(client_fd); + + continue; + } + + close(socket_fd); + free_modules(&context); +} diff --git a/zygiskd/src/zygiskd.h b/zygiskd/src/zygiskd.h new file mode 100644 index 00000000..868556a2 --- /dev/null +++ b/zygiskd/src/zygiskd.h @@ -0,0 +1,6 @@ +#ifndef ZYGISKD_H +#define ZYGISKD_H + +void zygiskd_start(char *restrict argv[]); + +#endif /* ZYGISKD_H */ diff --git a/zygiskd/src/zygiskd.rs b/zygiskd/src/zygiskd.rs deleted file mode 100644 index a3420523..00000000 --- a/zygiskd/src/zygiskd.rs +++ /dev/null @@ -1,354 +0,0 @@ -use crate::constants::{DaemonSocketAction, ProcessFlags}; -use crate::utils::{check_unix_socket, LateInit, UnixStreamExt}; -use crate::{constants, lp_select, root_impl, utils}; -use anyhow::{bail, Result}; -use log::{debug, error, info, trace, warn}; -use passfd::FdPassingExt; -use rustix::fs::{fcntl_setfd, FdFlags}; -use std::fs; -use std::io::Error; -use std::ops::Deref; -use std::os::fd::{AsFd, OwnedFd, RawFd}; -use std::os::unix::process::CommandExt; -use std::os::unix::{ - net::{UnixListener, UnixStream}, - prelude::AsRawFd, -}; -use std::path::PathBuf; -use std::process::{exit, Command}; -use std::sync::{Arc, Mutex}; -use std::thread; - -struct Module { - name: String, - lib_fd: OwnedFd, - companion: Mutex>>, -} - -struct Context { - modules: Vec, -} - -static TMP_PATH: LateInit = LateInit::new(); -static CONTROLLER_SOCKET: LateInit = LateInit::new(); -static PATH_CP_NAME: LateInit = LateInit::new(); - -pub fn main() -> Result<()> { - info!("Welcome to ReZygisk ({}) !", constants::ZKSU_VERSION); - - TMP_PATH.init(std::env::var("TMP_PATH")?); - CONTROLLER_SOCKET.init(format!("{}/init_monitor", TMP_PATH.deref())); - PATH_CP_NAME.init(format!( - "{}/{}", - TMP_PATH.deref(), - lp_select!("/cp32.sock", "/cp64.sock") - )); - - let arch = get_arch()?; - debug!("Daemon architecture: {arch}"); - let modules = load_modules(arch)?; - - { - let mut msg = Vec::::new(); - let info = match root_impl::get_impl() { - root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk | root_impl::RootImpl::APatch => { - msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes()); - let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect(); - format!( - "Root: {:?},module({}): {}", - root_impl::get_impl(), - modules.len(), - module_names.join(",") - ) - } - _ => { - msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes()); - format!("Invalid root implementation: {:?}", root_impl::get_impl()) - } - }; - msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes()); - msg.extend_from_slice(info.as_bytes()); - msg.extend_from_slice(&[0u8]); - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, msg.as_slice()) - .expect("failed to send info"); - } - - let context = Context { modules }; - let context = Arc::new(context); - let listener = create_daemon_socket()?; - for stream in listener.incoming() { - let mut stream = stream?; - let context = Arc::clone(&context); - let action = stream.read_u8()?; - let action = DaemonSocketAction::try_from(action)?; - trace!("New daemon action {:?}", action); - match action { - DaemonSocketAction::PingHeartbeat => { - let value = constants::ZYGOTE_INJECTED; - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?; - } - DaemonSocketAction::ZygoteRestart => { - info!("Zygote restarted, clean up companions"); - for module in &context.modules { - let mut companion = module.companion.lock().unwrap(); - companion.take(); - } - } - DaemonSocketAction::SystemServerStarted => { - let value = constants::SYSTEM_SERVER_STARTED; - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?; - } - _ => { - thread::spawn(move || { - if let Err(e) = handle_daemon_action(action, stream, &context) { - warn!("Error handling daemon action: {}\n{}", e, e.backtrace()); - } - }); - } - } - } - - Ok(()) -} - -fn get_arch() -> Result<&'static str> { - let system_arch = utils::get_property("ro.product.cpu.abi")?; - if system_arch.contains("arm") { - return Ok(lp_select!("armeabi-v7a", "arm64-v8a")); - } - if system_arch.contains("x86") { - return Ok(lp_select!("x86", "x86_64")); - } - bail!("Unsupported system architecture: {}", system_arch); -} - -fn load_modules(arch: &str) -> Result> { - let mut modules = Vec::new(); - let dir = match fs::read_dir(constants::PATH_MODULES_DIR) { - Ok(dir) => dir, - Err(e) => { - warn!("Failed reading modules directory: {}", e); - return Ok(modules); - } - }; - for entry in dir.into_iter() { - let entry = entry?; - let name = entry.file_name().into_string().unwrap(); - let so_path = entry.path().join(format!("zygisk/{arch}.so")); - let disabled = entry.path().join("disable"); - if !so_path.exists() || disabled.exists() { - continue; - } - info!(" Loading module `{name}`..."); - let lib_fd = match create_library_fd(&so_path) { - Ok(fd) => fd, - Err(e) => { - warn!(" Failed to create memfd for `{name}`: {e}"); - continue; - } - }; - let companion = Mutex::new(None); - let module = Module { - name, - lib_fd, - companion, - }; - modules.push(module); - } - - Ok(modules) -} - -fn create_library_fd(so_path: &PathBuf) -> Result { - let opts = memfd::MemfdOptions::default().allow_sealing(true); - let memfd = opts.create("jit-cache-zygisk")?; - let file = fs::File::open(so_path)?; - let mut reader = std::io::BufReader::new(file); - let mut writer = memfd.as_file(); - std::io::copy(&mut reader, &mut writer)?; - - let mut seals = memfd::SealsHashSet::new(); - seals.insert(memfd::FileSeal::SealShrink); - seals.insert(memfd::FileSeal::SealGrow); - seals.insert(memfd::FileSeal::SealWrite); - seals.insert(memfd::FileSeal::SealSeal); - memfd.add_seals(&seals)?; - - Ok(OwnedFd::from(memfd.into_file())) -} - -fn create_daemon_socket() -> Result { - utils::set_socket_create_context("u:r:zygote:s0")?; - let listener = utils::unix_listener_from_path(&PATH_CP_NAME)?; - Ok(listener) -} - -fn spawn_companion(name: &str, lib_fd: RawFd) -> Result> { - let (mut daemon, companion) = UnixStream::pair()?; - - // FIXME: avoid getting self path from arg0 - let process = std::env::args().next().unwrap(); - let nice_name = process.split('/').last().unwrap(); - - unsafe { - let pid = libc::fork(); - if pid < 0 { - bail!(Error::last_os_error()); - } else if pid > 0 { - drop(companion); - let mut status: libc::c_int = 0; - libc::waitpid(pid, &mut status, 0); - if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 { - daemon.write_string(name)?; - daemon.send_fd(lib_fd)?; - return match daemon.read_u8()? { - 0 => Ok(None), - 1 => Ok(Some(daemon)), - _ => bail!("Invalid companion response"), - }; - } else { - bail!("exited with status {}", status); - } - } else { - // Remove FD_CLOEXEC flag - fcntl_setfd(companion.as_fd(), FdFlags::empty())?; - } - } - - Command::new(&process) - .arg0(format!("{}-{}", nice_name, name)) - .arg("companion") - .arg(format!("{}", companion.as_raw_fd())) - .spawn()?; - exit(0) -} - -fn handle_daemon_action( - action: DaemonSocketAction, - mut stream: UnixStream, - context: &Context, -) -> Result<()> { - match action { - DaemonSocketAction::RequestLogcatFd => loop { - let level = match stream.read_u8() { - Ok(level) => level, - Err(_) => break, - }; - let tag = stream.read_string()?; - let message = stream.read_string()?; - utils::log_raw(level as i32, &tag, &message)?; - }, - DaemonSocketAction::GetProcessFlags => { - let uid = stream.read_u32()? as i32; - let mut flags = ProcessFlags::empty(); - if root_impl::uid_is_manager(uid) { - flags |= ProcessFlags::PROCESS_IS_MANAGER; - } else { - if root_impl::uid_granted_root(uid) { - flags |= ProcessFlags::PROCESS_GRANTED_ROOT; - } - if root_impl::uid_should_umount(uid) { - flags |= ProcessFlags::PROCESS_ON_DENYLIST; - } - } - match root_impl::get_impl() { - root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU, - root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK, - root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH, - _ => panic!("wrong root impl: {:?}", root_impl::get_impl()), - } - trace!( - "Uid {} granted root: {}", - uid, - flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT) - ); - trace!( - "Uid {} on denylist: {}", - uid, - flags.contains(ProcessFlags::PROCESS_ON_DENYLIST) - ); - stream.write_u32(flags.bits())?; - } - DaemonSocketAction::GetInfo => { - let mut flags = ProcessFlags::empty(); - - match root_impl::get_impl() { - root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU, - root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK, - root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH, - _ => panic!("wrong root impl: {:?}", root_impl::get_impl()), - } - - stream.write_u32(flags.bits())?; - - let pid = unsafe { libc::getpid() }; - stream.write_u32(pid as u32)?; - - stream.write_usize(context.modules.len())?; - - for module in context.modules.iter() { - stream.write_string(&module.name)?; - } - } - DaemonSocketAction::ReadModules => { - stream.write_usize(context.modules.len())?; - for module in context.modules.iter() { - stream.write_string(&module.name)?; - stream.send_fd(module.lib_fd.as_raw_fd())?; - } - } - DaemonSocketAction::RequestCompanionSocket => { - let index = stream.read_usize()?; - let module = &context.modules[index]; - let mut companion = module.companion.lock().unwrap(); - if let Some(Some(sock)) = companion.as_ref() { - if !check_unix_socket(sock, false) { - error!("Poll companion for module `{}` crashed", module.name); - companion.take(); - } - } - if companion.is_none() { - match spawn_companion(&module.name, module.lib_fd.as_raw_fd()) { - Ok(c) => { - if c.is_some() { - trace!(" Spawned companion for `{}`", module.name); - } else { - trace!( - " No companion spawned for `{}` because it has not entry", - module.name - ); - } - *companion = Some(c); - } - Err(e) => { - warn!(" Failed to spawn companion for `{}`: {}", module.name, e); - } - }; - } - match companion.as_ref() { - Some(Some(sock)) => { - if let Err(e) = sock.send_fd(stream.as_raw_fd()) { - error!( - "Failed to send companion fd socket of module `{}`: {}", - module.name, e - ); - stream.write_u8(0)?; - } - // Ok: Send by companion - } - _ => { - stream.write_u8(0)?; - } - } - } - DaemonSocketAction::GetModuleDir => { - let index = stream.read_usize()?; - let module = &context.modules[index]; - let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name); - let dir = fs::File::open(dir)?; - stream.send_fd(dir.as_raw_fd())?; - } - _ => {} - } - Ok(()) -}