Compare commits

...

38 Commits

Author SHA1 Message Date
Nullptr
e8958e94b6 Bump to 0.7.1 2023-06-19 11:08:41 +08:00
5ec1cff
0338cdb0ed Update kernelsu.rs (#34) 2023-06-18 16:42:54 +08:00
Nullptr
99e653c576 Revert "Fix #27: bool on kernel is 4 bytes, while rust's is 1 byte. This causes the stack to be covered, triggering a ub."
This reverts commit 7c27c32861.
2023-06-18 15:47:12 +08:00
Nullptr
7c27c32861 Fix #27: bool on kernel is 4 bytes, while rust's is 1 byte. This causes the stack to be covered, triggering a ub. 2023-06-13 23:32:43 +08:00
Nullptr
bea5ed47b8 Bump to 0.7.0 2023-06-04 11:16:03 +08:00
Nullptr
954a712089 Use app profile 2023-06-04 01:31:12 +08:00
Nullptr
f6195ddb43 Don't spawn new process for companion 2023-05-21 20:14:36 +08:00
Nullptr
8b5e9db347 Make watchdog async 2023-05-21 19:38:24 +08:00
Nullptr
a04f636ac4 Refine code 2023-05-19 19:14:54 +08:00
5ec1cff
00f0a6e3fa several changes (#21)
* umount for ksu 10763

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add crashdump sepolicy

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add more information about debug

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* daemonize zygiskd companion

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* create zygiskd if crash

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* injector: use ANDROID_DLEXT_USE_LIBRARY_FD to load module

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* zygiskd: use file as module fd instead of memfd on debug build

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* use OwnedFd

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* dlopen: no need to create ns

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-05-19 18:28:56 +08:00
Nullptr
f5bf82fa93 Set memfd name to module name on debug 2023-04-23 09:15:53 +08:00
Nullptr
33c4ea3c62 Bump to 0.6.5 2023-04-21 16:19:12 +08:00
Nullptr
3eee57eb8f Fix incorrect ksu denylist meaning 2023-04-21 08:40:11 +08:00
5ec1cff
250b4b2f8c umount for ksu 10763 (#20)
Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-04-20 13:36:08 +08:00
Howard Wu
3772e23473 Add issue templates (#19) 2023-04-16 14:41:56 +08:00
Nullptr
8c5acf1ebe Always add sepolicy 2023-04-14 10:43:18 +08:00
Nullptr
9d0858be7c Bump to 0.6.4 2023-03-23 19:55:15 +08:00
Nullptr
b7bed4ad35 Fix pltHookCommit 2023-03-23 19:54:45 +08:00
Nullptr
80b19c4412 Bump to 0.6.3 2023-03-21 23:08:11 +08:00
Nullptr
a6f455218f No inline for root_impl 2023-03-21 22:55:14 +08:00
Nullptr
87cf885070 No hex patch 2023-03-20 17:36:59 +08:00
5ec1cff
b775d28c23 Add CI (#14)
* CI

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Update gradle.properties

* add rustup targets (#1)

* Update ci.yml

* Update ci.yml

* Update ci.yml

* Use ccache and rust-cache

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
Co-authored-by: Mufanc <47652878+Mufanc@users.noreply.github.com>
2023-03-16 17:26:29 +08:00
5ec1cff
bf72296d33 Fix revert umount sepolicy (#12)
* Fix revert umount sepolicy

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Update sepolicy.rule

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-03-13 18:10:30 +08:00
Nullptr
9d648d9aa4 Bump to 0.6.2 2023-03-03 18:20:50 +08:00
Nullptr
843086f6f3 Add more sepolicy 2023-03-03 18:20:50 +08:00
Nullptr
49e3ac9d7a Fix dependency bug: OnceCell crashes on 32 bit 2023-03-03 18:20:36 +08:00
Nullptr
446ed92f26 Change module dir and bump to 0.6.1 2023-03-01 12:42:12 +08:00
Nullptr
2e9cbf79a7 Bump to 0.6.0 2023-02-28 21:32:13 +08:00
Nullptr
cce8e6686f Implement uid_on_allowlist for Magisk 2023-02-28 20:48:32 +08:00
Nullptr
ff2658f2de Rename libs 2023-02-28 20:48:20 +08:00
Nullptr
f465cbf810 Refactor to better support Magisk 2023-02-28 19:50:41 +08:00
Nullptr
09b6673ab0 Implement revert_unmount_magisk 2023-02-28 16:03:41 +08:00
Nullptr
5f8eb4af09 No submodule 2023-02-28 12:37:58 +08:00
Nullptr
8affc8f991 Fix zygote restart & Show zygisksu status on module.prop 2023-02-26 11:54:52 +08:00
Nullptr
ec8475bca5 Better unmount refine 2023-02-25 13:59:37 +08:00
Nullptr
9ff1e27a7d Unmount everything under ksu loop 2023-02-25 11:00:35 +08:00
Nullptr
6d9cc560cc Bump to 0.5.1 2023-02-24 22:24:45 +08:00
Nullptr
f395cfb490 Fix stupid remount bug 2023-02-24 16:43:53 +08:00
49 changed files with 1101 additions and 495 deletions

72
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Bug report/反馈 Bug
description: Report errors or unexpected behavior./反馈错误或异常行为。
labels: [bug]
title: "[Bug] Short description/简单描述"
body:
- type: markdown
attributes:
value: |
Thanks for reporting issues of Zygisk on KernelSU!
To make it easier for us to help you please enter detailed information below.
感谢给 Zygisk on KernelSU 汇报问题!
为了使我们更好地帮助你,请提供以下信息。
为了防止重复汇报,标题请务必使用英文。
- type: textarea
attributes:
label: Steps to reproduce/复现步骤
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour/预期行为
placeholder: Tell us what should happen/正常情况下应该发生什么
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour/实际行为
placeholder: Tell us what happens instead/实际上发生了什么
validations:
required: true
- type: textarea
attributes:
label: KernelSU Module List/KernelSU 模块列表
render: Shell
validations:
required: true
- type: input
attributes:
label: Zygisk on KernelSU version/Zygisk on KernelSU 版本
description: Don't use 'latest'. Specify actual version, otherwise your issue will be closed./不要填用“最新版”。给出具体版本号,不然 issue 会被关闭。
validations:
required: true
- type: input
attributes:
label: Android version/Android 版本
validations:
required: true
- type: input
attributes:
label: KernelSU version/KernelSU 版本
validations:
required: true
- type: checkboxes
id: latest
attributes:
label: Version requirement/版本要求
options:
- label: I am using latest debug CI version of Zygisk on KernelSU and enable verbose log/我正在使用最新 CI 调试版本 Zygisk on KernelSU 且启用详细日志
required: true
- type: textarea
attributes:
label: Logs/日志
description: For usage issues, please provide the log zip saved from KernelSU manager; for activation issues, please provide [bugreport](https://developer.android.com/studio/debug/bug-report). Without logs zip, the issue will be closed. /使用问题请提供从 KernelSU 管理器保存的日志压缩包;激活问题请提供 [bugreport](https://developer.android.google.cn/studio/debug/bug-report?hl=zh-cn) 日志。没有日志附件的问题会被关闭。
placeholder: Upload logs zip by clicking the bar on the bottom. Upload logs to other websites or using external links is prohibited. /点击文本框底栏上传日志压缩包,禁止上传到其它网站或使用外链提供日志。
validations:
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -0,0 +1,23 @@
name: Feature request/新特性请求
description: Suggest an idea./提出建议
labels: [enhancement]
title: "[Feature Request] Short description/简单描述"
body:
- type: textarea
attributes:
label: Is your feature request related to a problem?/你的请求是否与某个问题相关?
placeholder: A clear and concise description of what the problem is./请清晰准确表述该问题。
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like/描述你想要的解决方案
placeholder: A clear and concise description of what you want to happen./请清晰准确描述新特性的预期行为
validations:
required: true
- type: textarea
attributes:
label: Additional context/其他信息
placeholder: Add any other context or screenshots about the feature request here./其他关于新特性的信息或者截图
validations:
required: false

88
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: CI
on:
workflow_dispatch:
push:
branches: [ master ]
tags: [ v* ]
pull_request:
merge_group:
jobs:
build:
runs-on: ubuntu-latest
env:
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
CCACHE_BASEDIR: "${{ github.workspace }}"
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
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 target add armv7-linux-androideabi
rustup target add aarch64-linux-android
rustup target add x86_64-linux-android
rustup target add i686-linux-android
- name: Set up ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
max-size: 2G
key: ${{ runner.os }}
restore-keys: ${{ runner.os }}
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
- name: Build with Gradle
run: |
echo 'org.gradle.parallel=true' >> gradle.properties
echo 'org.gradle.vfs.watch=true' >> gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
echo 'android.native.buildOutput=verbose' >> gradle.properties
sed -i 's/org.gradle.unsafe.configuration-cache=true//g' gradle.properties
./gradlew zipRelease
./gradlew zipDebug
- name: Prepare artifact
if: success()
id: prepareArtifact
run: |
releaseName=`ls module/build/outputs/release/Zygisk-on-KernelSU-v*-release.zip | awk -F '(/|.zip)' '{print $5}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
debugName=`ls module/build/outputs/release/Zygisk-on-KernelSU-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT
unzip module/build/outputs/release/Zygisk-on-KernelSU-v*-release.zip -d zksu-release
unzip module/build/outputs/release/Zygisk-on-KernelSU-v*-debug.zip -d zksu-debug
- name: Upload release
uses: actions/upload-artifact@v3
with:
name: ${{ steps.prepareArtifact.outputs.releaseName }}
path: "./zksu-release/*"
- name: Upload debug
uses: actions/upload-artifact@v3
with:
name: ${{ steps.prepareArtifact.outputs.debugName }}
path: "./zksu-debug/*"

23
.github/workflows/issue_moderator.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Check issue
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
auto-close-rules: |
[
{
"type": "title",
"regex": ".*(Short description|简单描述).*",
"message": "You did not fill out the description in the title/你没有填写标题"
}
]
auto-close-ignore-label: do-not-autoclose

9
.gitmodules vendored
View File

@@ -1,9 +1,6 @@
[submodule "loader/src/external/liblsplt"]
path = loader/src/external/liblsplt
url = https://github.com/LSPosed/LSPlt
[submodule "loader/src/external/lsplt"]
path = loader/src/external/lsplt
url = https://github.com/LSPosed/lsplt
[submodule "loader/src/external/parallel-hashmap"]
path = loader/src/external/parallel-hashmap
url = https://github.com/greg7mdp/parallel-hashmap
[submodule "zygiskd/src/external/binder_rs"]
path = zygiskd/src/external/binder_rs
url = https://github.com/Kernel-SU/binder_rs

View File

@@ -2,15 +2,25 @@
Zygisk loader for KernelSU, allowing Zygisk modules to run without Magisk environment.
Also works as standalone loader for Magisk on purpose of getting rid of LD_PRELOAD.
Also works as standalone loader for Magisk.
## Requirements
+ Minimal KernelSU version: 10654
+ Minimal ksud version: 10647
+ Full SELinux patch support (If non-gki kernel)
### General
+ No multiple root implementation installed
### KernelSU
+ Minimal KernelSU version: 10940
+ Minimal ksud version: 10942
+ Kernel has full SELinux patch support
### Magisk
+ Minimal version: 25208
+ Original Zygisk turned off
## Compatibility
Should work with everything except those rely on Magisk internal behaviors.

View File

@@ -31,12 +31,12 @@ val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
val moduleId by extra("zygisksu")
val moduleName by extra("Zygisk on KernelSU")
val verName by extra("v4-0.5.0")
val verName by extra("v4-0.7.1")
val verCode by extra(gitCommitCount)
val minKsuVersion by extra(10654)
val minKsudVersion by extra(10647)
val minKsuVersion by extra(10940)
val minKsudVersion by extra(10942)
val maxKsuVersion by extra(20000)
val minMagiskVersion by extra(25000)
val minMagiskVersion by extra(25208)
val androidMinSdkVersion by extra(29)
val androidTargetSdkVersion by extra(33)

View File

@@ -20,4 +20,4 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,7 +1,26 @@
import java.nio.file.Paths
import org.gradle.internal.os.OperatingSystem
plugins {
id("com.android.library")
}
fun Project.findInPath(executable: String, property: String): String? {
val pathEnv = System.getenv("PATH")
return pathEnv.split(File.pathSeparator).map { folder ->
Paths.get("${folder}${File.separator}${executable}${if (OperatingSystem.current().isWindows) ".exe" else ""}")
.toFile()
}.firstOrNull { path ->
path.exists()
}?.absolutePath ?: properties.getOrDefault(property, null) as? String?
}
val ccachePatch by lazy {
project.findInPath("ccache", "ccache.path")?.also {
println("loader: Use ccache: $it")
}
}
android {
buildFeatures {
androidResources = false
@@ -12,6 +31,16 @@ android {
externalNativeBuild.ndkBuild {
path("src/Android.mk")
}
defaultConfig {
externalNativeBuild {
ndkBuild {
ccachePatch?.let {
arguments += "NDK_CCACHE=$it"
}
}
}
}
}
dependencies {

View File

@@ -13,7 +13,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygiskloader
LOCAL_MODULE := zygisk_loader
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/loader))
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
@@ -22,7 +22,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := injector
LOCAL_MODULE := zygisk_injector
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector))
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)

View File

@@ -8,19 +8,37 @@
namespace zygiskd {
bool sMagicRead = false;
static std::string sSocketName;
void ReadMagic() {
sMagicRead = true;
char magic[PATH_MAX]{0};
auto fp = fopen(kZygiskMagic, "r");
if (fp == nullptr) {
PLOGE("Open magic file");
return;
}
fgets(magic, PATH_MAX, fp);
fclose(fp);
sSocketName.append(LP_SELECT("zygiskd32", "zygiskd64")).append(magic);
LOGD("Socket name: %s", sSocketName.data());
}
int Connect(uint8_t retry) {
if (!sMagicRead) ReadMagic();
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
struct sockaddr_un addr{
.sun_family = AF_UNIX,
.sun_path={0},
};
strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size());
strcpy(addr.sun_path + 1, sSocketName.data());
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
while (retry--) {
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
if (r == 0) return fd;
LOGW("retrying to connect to zygiskd, sleep 1s");
LOGW("Retrying to connect to zygiskd, sleep 1s");
sleep(1);
}
@@ -97,6 +115,7 @@ namespace zygiskd {
if (socket_utils::read_u8(fd) == 1) {
return fd;
} else {
close(fd);
return -1;
}
}

View File

@@ -44,8 +44,17 @@ void* DlopenExt(const char* path, int flags) {
return handle;
}
void* DlopenMem(int memfd, int flags) {
char path[PATH_MAX];
sprintf(path, "/proc/self/fd/%d", memfd);
return DlopenExt(path, flags);
void* DlopenMem(int fd, int flags) {
auto info = android_dlextinfo{
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd
};
auto* handle = android_dlopen_ext("/jit-cache", flags, &info);
if (handle) {
LOGD("dlopen fd %d: %p", fd, handle);
} else {
LOGE("dlopen fd %d: %s", fd, dlerror());
}
return handle;
}

View File

@@ -3,18 +3,19 @@ LOCAL_PATH := $(call my-dir)
# liblsplt.a
include $(CLEAR_VARS)
LOCAL_MODULE:= liblsplt
LOCAL_C_INCLUDES := $(LOCAL_PATH)/liblsplt/lsplt/src/main/jni/include
LOCAL_C_INCLUDES := $(LOCAL_PATH)/lsplt/lsplt/src/main/jni/include
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -DLOG_DISABLED
LOCAL_CPPFLAGS := -std=c++20
LOCAL_STATIC_LIBRARIES := libcxx
LOCAL_SRC_FILES := \
liblsplt/lsplt/src/main/jni/elf_util.cc \
liblsplt/lsplt/src/main/jni/lsplt.cc
lsplt/lsplt/src/main/jni/elf_util.cc \
lsplt/lsplt/src/main/jni/lsplt.cc
include $(BUILD_STATIC_LIBRARY)
# Header only library
include $(CLEAR_VARS)
LOCAL_MODULE:= libphmap
LOCAL_CFLAGS := -Wno-unused-value
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap
include $(BUILD_STATIC_LIBRARY)

1
loader/src/external/lsplt vendored Submodule

View File

@@ -26,14 +26,40 @@
#define ZYGISK_API_VERSION 4
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON!
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
static void example_handler(int socket) { ... }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
@@ -51,8 +77,26 @@ private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
@@ -84,7 +128,7 @@ namespace zygisk {
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs as the same privilege of the app's own code.
// This means that this method runs with the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method is called before the system server process is specialized.
@@ -219,7 +263,16 @@ namespace zygisk {
// will be set to nullptr.
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// For ELFs loaded in memory matching `inode`, replace function `symbol` with `newFunc`.
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
//
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
//
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
@@ -243,11 +296,11 @@ void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a socket that is connected to the target process.
// which is a Unix domain socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have a globally shared resource.
// Be aware of race conditions if you have globally shared resources.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }

View File

@@ -10,7 +10,8 @@
#else
# define LP_SELECT(lp32, lp64) lp32
#endif
constexpr std::string_view kZygiskSocket = LP_SELECT("zygiskd32", "zygiskd64") "socket_placeholder";
constexpr auto kZygiskMagic = "/system/zygisk_magic";
class UniqueFd {
using Fd = int;
@@ -19,7 +20,7 @@ public:
UniqueFd(Fd fd) : fd_(fd) {}
~UniqueFd() { close(fd_); }
~UniqueFd() { if (fd_ >= 0) close(fd_); }
// Disallow copy
UniqueFd(const UniqueFd&) = delete;

View File

@@ -0,0 +1,122 @@
#include <sys/sysmacros.h>
#include "files.hpp"
#include "misc.hpp"
using namespace std::string_view_literals;
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn) {
size_t len = 1024;
char *buf = (char *) malloc(len);
char *start;
ssize_t read;
while ((read = getline(&buf, &len, fp)) >= 0) {
start = buf;
if (trim) {
while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos)
--read;
buf[read] = '\0';
while (*start == ' ')
++start;
}
if (!fn(start))
break;
}
free(buf);
}
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn) {
if (auto fp = open_file(file, "re"))
file_readline(trim, fp.get(), fn);
}
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn) {
file_readline(false, file, fn);
}
std::vector<mount_info> parse_mount_info(const char *pid) {
char buf[PATH_MAX] = {};
snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid);
std::vector<mount_info> result;
file_readline(buf, [&result](std::string_view line) -> bool {
int root_start = 0, root_end = 0;
int target_start = 0, target_end = 0;
int vfs_option_start = 0, vfs_option_end = 0;
int type_start = 0, type_end = 0;
int source_start = 0, source_end = 0;
int fs_option_start = 0, fs_option_end = 0;
int optional_start = 0, optional_end = 0;
unsigned int id, parent, maj, min;
sscanf(line.data(),
"%u " // (1) id
"%u " // (2) parent
"%u:%u " // (3) maj:min
"%n%*s%n " // (4) mountroot
"%n%*s%n " // (5) target
"%n%*s%n" // (6) vfs options (fs-independent)
"%n%*[^-]%n - " // (7) optional fields
"%n%*s%n " // (8) FS type
"%n%*s%n " // (9) source
"%n%*s%n", // (10) fs options (fs specific)
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
&target_end, &vfs_option_start, &vfs_option_end,
&optional_start, &optional_end, &type_start, &type_end,
&source_start, &source_end, &fs_option_start, &fs_option_end);
auto root = line.substr(root_start, root_end - root_start);
auto target = line.substr(target_start, target_end - target_start);
auto vfs_option =
line.substr(vfs_option_start, vfs_option_end - vfs_option_start);
++optional_start;
--optional_end;
auto optional = line.substr(
optional_start,
optional_end - optional_start > 0 ? optional_end - optional_start : 0);
auto type = line.substr(type_start, type_end - type_start);
auto source = line.substr(source_start, source_end - source_start);
auto fs_option =
line.substr(fs_option_start, fs_option_end - fs_option_start);
unsigned int shared = 0;
unsigned int master = 0;
unsigned int propagate_from = 0;
if (auto pos = optional.find("shared:"); pos != std::string_view::npos) {
shared = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("master:"); pos != std::string_view::npos) {
master = parse_int(optional.substr(pos + 7));
}
if (auto pos = optional.find("propagate_from:");
pos != std::string_view::npos) {
propagate_from = parse_int(optional.substr(pos + 15));
}
result.emplace_back(mount_info {
.id = id,
.parent = parent,
.device = static_cast<dev_t>(makedev(maj, min)),
.root {root},
.target {target},
.vfs_option {vfs_option},
.optional {
.shared = shared,
.master = master,
.propagate_from = propagate_from,
},
.type {type},
.source {source},
.fs_option {fs_option},
});
return true;
});
return result;
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}
sFILE make_file(FILE *fp) {
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
}

View File

@@ -0,0 +1,56 @@
#include <dirent.h>
#include <functional>
#include <string>
#include <vector>
struct mount_info {
unsigned int id;
unsigned int parent;
dev_t device;
std::string root;
std::string target;
std::string vfs_option;
struct {
unsigned int shared;
unsigned int master;
unsigned int propagate_from;
} optional;
std::string type;
std::string source;
std::string fs_option;
};
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
std::vector<mount_info> parse_mount_info(const char *pid);
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
sDIR make_dir(DIR *dp);
sFILE make_file(FILE *fp);
static inline sDIR open_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(int dirfd) {
return make_dir(fdopendir(dirfd));
}
static inline sFILE open_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(int fd, const char *mode) {
return make_file(fdopen(fd, mode));
}

View File

@@ -17,7 +17,7 @@
#include "zygisk.hpp"
#include "memory.hpp"
#include "module.hpp"
#include "misc.hpp"
#include "files.hpp"
using namespace std;
using jni_hook::hash_map;
@@ -219,7 +219,11 @@ DCL_HOOK_FUNC(int, unshare, int flags) {
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
(g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) {
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
revert_unmount();
if (g_ctx->info_flags & PROCESS_ROOT_IS_KSU) {
revert_unmount_ksu();
} else if (g_ctx->info_flags & PROCESS_ROOT_IS_MAGISK) {
revert_unmount_magisk();
}
}
/* Zygisksu changed: No umount app_process */
@@ -327,6 +331,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
}
if (api_version >= 4) {
api->v4.pltHookCommit = lsplt::CommitHook;
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
return;

View File

@@ -23,18 +23,6 @@ int parse_int(std::string_view s) {
return val;
}
void parse_mnt(const char* file, const std::function<bool(mntent*)>& fn) {
auto fp = sFILE(setmntent(file, "re"), endmntent);
if (fp) {
mntent mentry{};
char buf[PATH_MAX];
while (getmntent_r(fp.get(), &mentry, buf, sizeof(buf))) {
if (!fn(&mentry))
break;
}
}
}
std::list<std::string> split_str(std::string_view s, std::string_view delimiter) {
std::list<std::string> ret;
size_t pos = 0;
@@ -59,11 +47,3 @@ std::string join_str(const std::list<std::string>& list, std::string_view delimi
}
return ret;
}
sDIR make_dir(DIR *dp) {
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
}
sFILE make_file(FILE *fp) {
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
}

View File

@@ -1,12 +1,8 @@
#pragma once
#include <dirent.h>
#include <functional>
#include <list>
#include <memory>
#include <mntent.h>
#include <pthread.h>
#include <stdio.h>
#include <string>
#include <string_view>
@@ -39,12 +35,6 @@ int new_daemon_thread(thread_entry entry, void *arg);
static inline bool str_contains(std::string_view s, std::string_view ss) {
return s.find(ss) != std::string_view::npos;
}
static inline bool str_starts(std::string_view s, std::string_view ss) {
return s.size() >= ss.size() && s.compare(0, ss.size(), ss) == 0;
}
static inline bool str_ends(std::string_view s, std::string_view ss) {
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string_view::npos, ss) == 0;
}
template<typename T, typename Impl>
class stateless_allocator {
@@ -61,35 +51,6 @@ public:
bool operator!=(const stateless_allocator&) { return false; }
};
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
sDIR make_dir(DIR *dp);
sFILE make_file(FILE *fp);
static inline sDIR open_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(const char *path) {
return make_dir(opendir(path));
}
static inline sDIR xopen_dir(int dirfd) {
return make_dir(fdopendir(dirfd));
}
static inline sFILE open_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(const char *path, const char *mode) {
return make_file(fopen(path, mode));
}
static inline sFILE xopen_file(int fd, const char *mode) {
return make_file(fdopen(fd, mode));
}
template <typename T>
class reversed_container {
public:
@@ -126,8 +87,6 @@ struct StringCmp {
*/
int parse_int(std::string_view s);
void parse_mnt(const char* file, const std::function<bool(mntent*)>& fn);
std::list<std::string> split_str(std::string_view s, std::string_view delimiter);
std::string join_str(const std::list<std::string>& list, std::string_view delimiter);

View File

@@ -111,6 +111,8 @@ namespace {
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
PROCESS_ROOT_IS_KSU = (1u << 29),
PROCESS_ROOT_IS_MAGISK = (1u << 30),
PROCESS_IS_SYS_UI = (1u << 31),
PRIVATE_MASK = PROCESS_IS_SYS_UI

View File

@@ -1,73 +1,76 @@
#include <mntent.h>
#include <sys/mount.h>
#include "files.hpp"
#include "logging.h"
#include "misc.hpp"
#include "zygisk.hpp"
using namespace std::string_view_literals;
static void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1) {
LOGD("Unmounted (%s)", mountpoint);
} else {
PLOGE("Failed to unmount: %s (%s)", strerror(errno), mountpoint);
namespace {
constexpr auto MODULE_DIR = "/data/adb/modules";
constexpr auto KSU_OVERLAY_SOURCE = "KSU";
const std::vector<std::string> KSU_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"};
void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1) {
LOGD("Unmounted (%s)", mountpoint);
} else {
PLOGE("Unmount (%s)", mountpoint);
}
}
}
#define PARSE_OPT(name, flag) \
if (opt == name) { \
flags |= (flag); \
return true; \
void revert_unmount_ksu() {
std::string ksu_loop;
std::vector<std::string> targets;
// Unmount ksu module dir last
targets.emplace_back(MODULE_DIR);
for (auto& info: parse_mount_info("self")) {
if (info.target == MODULE_DIR) {
ksu_loop = info.source;
continue;
}
// Unmount everything on /data/adb except ksu module dir
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
// Unmount ksu overlays
if (info.type == "overlay"
&& info.source == KSU_OVERLAY_SOURCE
&& std::find(KSU_PARTITIONS.begin(), KSU_PARTITIONS.end(), info.target) != KSU_PARTITIONS.end()) {
targets.emplace_back(info.target);
}
}
for (auto& info: parse_mount_info("self")) {
// Unmount everything from ksu loop except ksu module dir
if (info.source == ksu_loop && info.target != MODULE_DIR) {
targets.emplace_back(info.target);
}
}
void revert_unmount() {
std::vector<std::string> targets;
std::list<std::pair<std::string, std::string>> backups;
// Do unmount
for (auto& s: reversed(targets)) {
lazy_unmount(s.data());
}
}
targets.emplace_back("/data/adb/ksu/modules");
parse_mnt("/proc/self/mounts", [&](mntent* mentry) {
if (str_starts(mentry->mnt_fsname, "/data/adb/")) {
targets.emplace_back(mentry->mnt_dir);
void revert_unmount_magisk() {
std::vector<std::string> targets;
// Unmount dummy skeletons and MAGISKTMP
// since mirror nodes are always mounted under skeleton, we don't have to specifically unmount
for (auto& info: parse_mount_info("self")) {
if (info.source == "magisk" || info.source == "worker" || // magisktmp tmpfs
info.root.starts_with("/adb/modules")) { // bind mount from data partition
targets.push_back(info.target);
}
if (mentry->mnt_type == "overlay"sv) {
if (str_contains(mentry->mnt_opts, "/data/adb/ksu/modules")) {
targets.emplace_back(mentry->mnt_dir);
} else {
backups.emplace_back(mentry->mnt_dir, mentry->mnt_opts);
}
}
return true;
});
}
for (auto& s: reversed(targets)) {
lazy_unmount(s.data());
}
parse_mnt("/proc/self/mounts", [&](mntent* mentry) {
if (mentry->mnt_type == "overlay"sv) {
for (auto it = backups.begin(); it != backups.end(); it++) {
backups.remove_if([&](auto& mnt) {
return mnt.first == mentry->mnt_dir && mnt.second == mentry->mnt_opts;
});
}
}
return true;
});
for (auto& mnt: backups) {
auto opts = split_str(mnt.second, ",");
unsigned long flags = 0;
opts.remove_if([&](auto& opt) {
PARSE_OPT(MNTOPT_RO, MS_RDONLY)
PARSE_OPT(MNTOPT_NOSUID, MS_NOSUID)
PARSE_OPT("relatime", MS_RELATIME)
return false;
});
auto mnt_data = join_str(opts, ",");
if (mount("overlay", mnt.first.data(), "overlay", flags, mnt_data.data()) != -1) {
LOGD("Remounted (%s)", mnt.first.data());
} else {
LOGW("Failed to remount: %s (%s, %s)", strerror(errno), mnt.first.data(), mnt_data.data());
}
}
}

View File

@@ -8,4 +8,7 @@ extern void *self_handle;
void hook_functions();
void revert_unmount();
void revert_unmount_ksu();
void revert_unmount_magisk();

View File

@@ -13,7 +13,7 @@ uint8_t NativeBridgeItf[sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0}
namespace {
constexpr auto kZygoteProcesses = {"zygote", "zygote32", "zygote64", "usap32", "usap64"};
constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libinjector.so";
constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libzygisk_injector.so";
void* sOriginalBridge = nullptr;
}
@@ -70,13 +70,13 @@ void Constructor() {
LOGI("Load original native bridge: %s", native_bridge.data());
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
if (sOriginalBridge == nullptr) {
LOGE("dlopen failed: %s", dlerror());
LOGE("%s", dlerror());
break;
}
auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
if (original_native_bridge_itf == nullptr) {
LOGE("dlsym failed: %s", dlerror());
LOGE("%s", dlerror());
break;
}

View File

@@ -38,7 +38,7 @@ androidComponents.onVariants { variant ->
into(moduleDir)
from("${rootProject.projectDir}/README.md")
from("$projectDir/src") {
exclude("module.prop", "customize.sh", "daemon.sh")
exclude("module.prop", "customize.sh", "service.sh")
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
from("$projectDir/src") {
@@ -46,12 +46,12 @@ androidComponents.onVariants { variant ->
expand(
"moduleId" to moduleId,
"moduleName" to moduleName,
"versionName" to "$verName ($verCode)",
"versionName" to "$verName ($verCode-$variantLowered)",
"versionCode" to verCode,
)
}
from("$projectDir/src") {
include("customize.sh", "daemon.sh")
include("customize.sh", "service.sh")
val tokens = mapOf(
"DEBUG" to if (buildTypeLowered == "debug") "true" else "false",
"MIN_KSU_VERSION" to "$minKsuVersion",

View File

@@ -80,6 +80,11 @@ extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
if [ "$DEBUG" = true ]; then
ui_print "- Add debug SELinux policy"
echo "allow crash_dump adb_data_file dir search" >> "$TMPDIR/sepolicy.rule"
fi
if [ "$KSU" ]; then
ui_print "- Checking SELinux patches"
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
@@ -91,11 +96,10 @@ if [ "$KSU" ]; then
fi
ui_print "- Extracting module files"
extract "$ZIPFILE" 'daemon.sh' "$MODPATH"
extract "$ZIPFILE" 'module.prop' "$MODPATH"
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
extract "$ZIPFILE" 'service.sh' "$MODPATH"
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true
@@ -110,8 +114,8 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
ui_print "- Extracting x86 libraries"
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/x86/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygiskloader.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygisk_injector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygisk_loader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
@@ -119,8 +123,8 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/x86_64/libinjector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygiskloader.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygisk_injector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygisk_loader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
else
@@ -128,8 +132,8 @@ else
ui_print "- Extracting arm libraries"
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/armeabi-v7a/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygiskloader.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_injector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_loader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
@@ -137,26 +141,15 @@ else
ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/arm64-v8a/libinjector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygiskloader.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_injector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_loader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
fi
if [ $DEBUG = false ]; then
ui_print "- Hex patching"
SOCKET_PATCH=$(tr -dc 'a-f0-9' </dev/urandom | head -c 18)
if [ "$HAS32BIT" = true ]; then
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/bin/zygiskd32"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib/libinjector.so"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib/libzygiskloader.so"
fi
if [ "$HAS64BIT" = true ]; then
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/bin/zygiskd64"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib64/libinjector.so"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib64/libzygiskloader.so"
fi
fi
ui_print "- Generating magic"
MAGIC=$(tr -dc 'a-f0-9' </dev/urandom | head -c 18)
echo -n "$MAGIC" > "$MODPATH/system/zygisk_magic"
ui_print "- Setting permissions"
chmod 0744 "$MODPATH/daemon.sh"

View File

@@ -1,11 +0,0 @@
#!/system/bin/sh
DEBUG=@DEBUG@
# shellcheck disable=SC2155
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
log -p i -t "zygisksu" "Start watchdog"
resetprop ro.dalvik.vm.native.bridge libzygiskloader.so
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1

View File

@@ -6,9 +6,10 @@ if [ "$ZYGISK_ENABLED" ]; then
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
getprop ro.dalvik.vm.native.bridge > /dev/.native_bridge
resetprop ro.dalvik.vm.native.bridge libzygisk_loader.so
if [ "$(which magisk)" ] && [ ".." -ef "/data/adb/modules" ]; then
if [ "$(which magisk)" ]; then
for file in ../*; do
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
if [ -f "$file/post-fs-data.sh" ]; then
@@ -20,5 +21,3 @@ if [ "$(which magisk)" ] && [ ".." -ef "/data/adb/modules" ]; then
fi
done
fi
sh -c "./daemon.sh $@&"

View File

@@ -1,4 +1,6 @@
allow * tmpfs * *
allow zygote appdomain_tmpfs dir *
allow zygote appdomain_tmpfs file *
type magisk_file file_type
typeattribute magisk_file mlstrustedobject
@@ -10,6 +12,8 @@ allow * magisk_file lnk_file *
allow * magisk_file sock_file *
allow system_server system_server process execmem
allow zygote adb_data_file dir search
allow zygote mnt_vendor_file dir search
allow zygote system_file dir mounton
allow zygote labeledfs filesystem mount
allow zygote fs_type filesystem unmount

View File

@@ -1,13 +1,17 @@
#!/system/bin/sh
DEBUG=@DEBUG@
MODDIR=${0%/*}
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(cat /dev/.native_bridge)
rm /dev/.native_bridge
if [ "$(which magisk)" ] && [ ".." -ef "/data/adb/modules" ]; then
if [ "$(which magisk)" ]; then
for file in ../*; do
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
if [ -f "$file/service.sh" ]; then
@@ -19,3 +23,7 @@ if [ "$(which magisk)" ] && [ ".." -ef "/data/adb/modules" ]; then
fi
done
fi
log -p i -t "zygisksu" "Start watchdog"
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1

View File

@@ -7,8 +7,8 @@ pluginManagement {
gradlePluginPortal()
}
plugins {
id("com.android.library") version "7.4.1"
id("com.android.application") version "7.4.1"
id("com.android.library") version "7.4.2"
id("com.android.application") version "7.4.2"
}
}

View File

@@ -3,24 +3,27 @@ name = "zygiskd"
authors = ["Nullptr"]
version = "1.0.0"
edition = "2021"
rust-version = "1.67"
rust-version = "1.69"
[dependencies]
android_logger = "0.12.0"
anyhow = { version = "1.0.68", features = ["backtrace"] }
clap = { version = "4.1.4", features = ["derive"] }
const_format = "0.2.5"
konst = "0.3.4"
lazy_static = "1.4.0"
log = "0.4.17"
memfd = "0.6.2"
nix = "0.26.2"
num_enum = "0.5.9"
once_cell = "1.17.1"
passfd = "0.1.5"
rand = "0.8.5"
android_logger = "0.13"
anyhow = { version = "1.0", features = ["backtrace"] }
bitflags = { version = "2.3" }
clap = { version = "4", features = ["derive"] }
const_format = "0.2"
futures = "0.3"
konst = "0.3"
lazy_static = "1.4"
log = "0.4"
memfd = "0.6"
nix = { version = "0.26", features = ["process","poll"] }
num_enum = "0.5"
once_cell = "1.17"
passfd = "0.1"
rand = "0.8"
tokio = { version = "1.28", features = ["full"] }
binder = { path = "src/external/binder_rs/binder" }
binder = { git = "https://github.com/Kernel-SU/binder_rs" }
[profile.release]
strip = true

View File

@@ -1,61 +0,0 @@
use std::ffi::c_void;
use std::os::fd::{FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::thread;
use anyhow::Result;
use nix::libc;
use passfd::FdPassingExt;
use crate::utils::UnixStreamExt;
use crate::dl;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
pub fn entry(fd: i32) -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
let name = stream.read_string()?;
let library = stream.recv_fd()?;
let entry = load_module(library)?;
unsafe { libc::close(library) };
let entry = match entry {
Some(entry) => {
log::debug!("Companion process created for `{name}`");
stream.write_u8(1)?;
entry
}
None => {
log::debug!("No companion entry for `{name}`");
stream.write_u8(0)?;
return Ok(());
}
};
loop {
let fd = stream.recv_fd()?;
log::trace!("New companion request from module `{name}`");
thread::spawn(move || {
unsafe {
let mut s = UnixStream::from_raw_fd(fd);
match s.write_u8(1) { // Ack
Ok(_) => entry(fd),
Err(_) => log::warn!("Ack failed?"),
}
};
});
}
}
fn load_module(fd: RawFd) -> Result<Option<ZygiskCompanionEntryFn>> {
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))
}
}

View File

@@ -1,3 +1,4 @@
use bitflags::bitflags;
use const_format::concatcp;
use konst::primitive::parse_i32;
use konst::unwrap_ctx;
@@ -16,27 +17,23 @@ pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
#[cfg(not(debug_assertions))]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
#[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 };
}
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
pub const PROP_SVC_ZYGOTE: &str = "init.svc.zygote";
pub const ZYGISK_LOADER: &str = "libzygiskloader.so";
pub const PROP_CTL_RESTART: &str = "ctl.restart";
pub const ZYGISK_LOADER: &str = "libzygisk_loader.so";
pub const ZYGISK_MAGIC: &str = "/system/zygisk_magic";
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
pub const PATH_MODULE_DIR: &str = "..";
pub const PATH_MODULES_DIR: &str = "..";
pub const PATH_MODULE_PROP: &str = "module.prop";
pub const PATH_ZYGISKD32: &str = "bin/zygiskd32";
pub const PATH_ZYGISKD64: &str = "bin/zygiskd64";
pub const STATUS_LOADED: &str = "😋 Zygisksu is loaded";
pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed";
pub const STATUS_ROOT_IMPL_NONE: &str = "❌ Unknown root implementation";
pub const STATUS_ROOT_IMPL_TOO_OLD: &str = "❌ Root implementation version too old";
pub const STATUS_ROOT_IMPL_ABNORMAL: &str = "❌ Abnormal root implementation version";
pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations installed";
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum DaemonSocketAction {
@@ -50,6 +47,13 @@ pub enum DaemonSocketAction {
}
// Zygisk process flags
pub const PROCESS_GRANTED_ROOT: u32 = 1 << 0;
pub const PROCESS_ON_DENYLIST: u32 = 1 << 1;
pub const PROCESS_IS_SYSUI: u32 = 1 << 31;
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_ROOT_IS_KSU = 1 << 29;
const PROCESS_ROOT_IS_MAGISK = 1 << 30;
const PROCESS_IS_SYSUI = 1 << 31;
}
}

View File

@@ -76,7 +76,7 @@ pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
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!("dlopen failed: {}", e);
bail!(e);
}
Ok(result)
}

19
zygiskd/src/magic.rs Normal file
View File

@@ -0,0 +1,19 @@
use std::fs;
use anyhow::Result;
use crate::constants;
use crate::utils::LateInit;
pub static MAGIC: LateInit<String> = LateInit::new();
pub static PATH_TMP_DIR: LateInit<String> = LateInit::new();
pub static PATH_TMP_PROP: LateInit<String> = LateInit::new();
pub fn setup() -> Result<()> {
let name = fs::read_to_string(constants::ZYGISK_MAGIC)?;
let path_tmp_dir = format!("/dev/{}", name);
let path_tmp_prop = format!("{}/module.prop", path_tmp_dir);
MAGIC.init(name);
PATH_TMP_DIR.init(path_tmp_dir);
PATH_TMP_PROP.init(path_tmp_prop);
Ok(())
}

View File

@@ -1,8 +1,8 @@
#![allow(dead_code)]
mod companion;
mod constants;
mod dl;
mod magic;
mod root_impl;
mod utils;
mod watchdog;
@@ -24,8 +24,6 @@ enum Commands {
Watchdog,
/// Start zygisk daemon
Daemon,
/// Start zygisk companion
Companion { fd: i32 },
}
@@ -37,23 +35,24 @@ fn init_android_logger(tag: &str) {
);
}
fn start() -> Result<()> {
root_impl::setup()?;
async fn start() -> Result<()> {
root_impl::setup();
magic::setup()?;
let cli = Args::parse();
match cli.command {
Commands::Watchdog => watchdog::entry()?,
Commands::Watchdog => watchdog::entry().await?,
Commands::Daemon => zygiskd::entry()?,
Commands::Companion { fd } => companion::entry(fd)?,
};
Ok(())
}
fn main() {
#[tokio::main]
async fn main() {
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name);
if let Err(e) = start() {
if let Err(e) = start().await {
log::error!("Crashed: {}\n{}", e, e.backtrace());
}
}

View File

@@ -1,36 +1,37 @@
use anyhow::{bail, Result};
use nix::libc::prctl;
use crate::constants::{MIN_KSU_VERSION, MAX_KSU_VERSION};
const KERNEL_SU_OPTION: i32 = 0xdeadbeefu32 as i32;
const CMD_GET_VERSION: usize = 2;
const CMD_GET_ALLOW_LIST: usize = 5;
const CMD_GET_DENY_LIST: usize = 6;
const CMD_UID_GRANTED_ROOT: usize = 12;
const CMD_UID_SHOULD_UMOUNT: usize = 13;
pub fn is_kernel_su() -> Result<bool> {
pub enum Version {
Supported,
TooOld,
Abnormal,
}
pub fn get_kernel_su() -> Option<Version> {
let mut version = 0;
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &mut version as *mut i32) };
return match version {
0 => Ok(false),
MIN_KSU_VERSION..=MAX_KSU_VERSION => Ok(true),
1..=MIN_KSU_VERSION => bail!("KernelSU version too old: {}", version),
_ => bail!("KernelSU version abnormal: {}", version)
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &mut version as *mut i32, 0, 0) };
match version {
0 => None,
MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported),
1..=MIN_KSU_VERSION => Some(Version::TooOld),
_ => Some(Version::Abnormal)
}
}
pub fn uid_on_allowlist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_ALLOW_LIST, uids.as_mut_ptr(), &mut size as *mut u32) };
uids.resize(size as usize, 0);
uids.contains(&uid)
pub fn uid_granted_root(uid: i32) -> bool {
let mut granted = false;
unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &mut granted as *mut bool, 0) };
granted
}
pub fn uid_on_denylist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_DENY_LIST, uids.as_mut_ptr(), &mut size as *mut u32) };
uids.resize(size as usize, 0);
uids.contains(&uid)
pub fn uid_should_umount(uid: i32) -> bool {
let mut umount = false;
unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &mut umount as *mut bool, 0) };
umount
}

View File

@@ -1,8 +1,12 @@
use anyhow::{bail, Result};
use std::process::{Command, Stdio};
use crate::constants::MIN_MAGISK_VERSION;
pub fn is_magisk() -> Result<bool> {
pub enum Version {
Supported,
TooOld,
}
pub fn get_magisk() -> Option<Version> {
let version: Option<i32> = Command::new("magisk")
.arg("-V")
.stdout(Stdio::piped())
@@ -10,21 +14,33 @@ pub fn is_magisk() -> Result<bool> {
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.and_then(|output| output.trim().parse().ok());
if let Some(version) = version {
if version < MIN_MAGISK_VERSION {
bail!("Magisk version too old: {}", version);
version.map(|version| {
if version >= MIN_MAGISK_VERSION {
Version::Supported
} else {
Version::TooOld
}
return Ok(true);
}
Ok(false)
})
}
pub fn uid_on_allowlist(uid: i32) -> bool {
// TODO: uid_on_allowlist
return false;
pub fn uid_granted_root(uid: i32) -> bool {
let output: Option<String> = Command::new("magisk")
.arg("--sqlite")
.arg("select uid from policies where policy=2")
.stdout(Stdio::piped())
.spawn().ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok());
let lines = match &output {
Some(output) => output.lines(),
None => return false,
};
lines.into_iter().any(|line| {
line.trim().strip_prefix("uid=").and_then(|uid| uid.parse().ok()) == Some(uid)
})
}
pub fn uid_on_denylist(uid: i32) -> bool {
// TODO: uid_on_denylist
pub fn uid_should_umount(uid: i32) -> bool {
// TODO: uid_should_umount
return false;
}

View File

@@ -1,40 +1,58 @@
mod kernelsu;
mod magisk;
use once_cell::sync::OnceCell;
use anyhow::{bail, Result};
enum RootImpl {
pub enum RootImpl {
None,
TooOld,
Abnormal,
Multiple,
KernelSU,
Magisk,
}
static ROOT_IMPL: OnceCell<RootImpl> = OnceCell::new();
// FIXME: OnceCell bugs on 32 bit
static mut ROOT_IMPL: RootImpl = RootImpl::None;
pub fn setup() -> Result<()> {
if kernelsu::is_kernel_su()? {
if let Ok(true) = magisk::is_magisk() {
bail!("Multiple root implementation");
pub fn setup() {
let ksu_version = kernelsu::get_kernel_su();
let magisk_version = magisk::get_magisk();
let impl_ = match (ksu_version, magisk_version) {
(None, None) => RootImpl::None,
(Some(_), Some(_)) => RootImpl::Multiple,
(Some(ksu_version), None) => {
match ksu_version {
kernelsu::Version::Supported => RootImpl::KernelSU,
kernelsu::Version::TooOld => RootImpl::TooOld,
kernelsu::Version::Abnormal => RootImpl::Abnormal,
}
}
let _ = ROOT_IMPL.set(RootImpl::KernelSU);
} else if magisk::is_magisk()? {
let _ = ROOT_IMPL.set(RootImpl::Magisk);
} else {
bail!("Unknown root implementation");
}
Ok(())
(None, Some(magisk_version)) => {
match magisk_version {
magisk::Version::Supported => RootImpl::Magisk,
magisk::Version::TooOld => RootImpl::TooOld,
}
}
};
unsafe { ROOT_IMPL = impl_; }
}
pub fn uid_on_allowlist(uid: i32) -> bool {
match ROOT_IMPL.get().unwrap() {
RootImpl::KernelSU => kernelsu::uid_on_allowlist(uid),
RootImpl::Magisk => magisk::uid_on_allowlist(uid),
pub fn get_impl() -> &'static RootImpl {
unsafe { &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),
_ => unreachable!(),
}
}
pub fn uid_on_denylist(uid: i32) -> bool {
match ROOT_IMPL.get().unwrap() {
RootImpl::KernelSU => kernelsu::uid_on_denylist(uid),
RootImpl::Magisk => magisk::uid_on_denylist(uid),
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),
_ => unreachable!(),
}
}

View File

@@ -1,11 +1,56 @@
use anyhow::Result;
use nix::unistd::gettid;
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
use std::ffi::c_char;
use std::os::fd::FromRawFd;
use std::os::unix::net::UnixListener;
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
use once_cell::sync::OnceCell;
use rand::distributions::{Alphanumeric, DistString};
#[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<T> {
cell: OnceCell<T>,
}
impl<T> LateInit<T> {
pub const fn new() -> Self {
LateInit { cell: OnceCell::new() }
}
pub fn init(&self, value: T) {
assert!(self.cell.set(value).is_ok())
}
}
impl<T> std::ops::Deref for LateInit<T> {
type Target = T;
fn deref(&self) -> &T {
self.cell.get().unwrap()
}
}
pub fn random_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
}
@@ -26,6 +71,24 @@ pub fn get_native_bridge() -> String {
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
}
pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> {
let tag = std::ffi::CString::new(tag)?;
let message = std::ffi::CString::new(message)?;
unsafe {
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
}
Ok(())
}
pub fn get_property(name: &str) -> Result<String> {
let name = std::ffi::CString::new(name)?;
let mut buf = vec![0u8; 92];
unsafe {
__system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char);
}
Ok(String::from_utf8(buf)?)
}
pub fn set_property(name: &str, value: &str) -> Result<()> {
Command::new("resetprop")
.arg(name)
@@ -102,3 +165,8 @@ pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
let listener = unsafe { UnixListener::from_raw_fd(socket) };
Ok(listener)
}
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;
}

View File

@@ -1,21 +1,38 @@
use crate::{constants, utils};
use crate::{constants, magic, root_impl, utils};
use anyhow::{bail, Result};
use nix::unistd::{getgid, getuid, Pid};
use std::process::{Child, Command};
use std::sync::mpsc;
use std::{fs, thread};
use std::{fs, io};
use std::ffi::CString;
use std::future::Future;
use std::io::{BufRead, Write};
use std::os::unix::net::UnixListener;
use std::pin::Pin;
use std::time::Duration;
use binder::IBinder;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nix::errno::Errno;
use nix::libc;
use nix::sys::signal::{kill, Signal};
use tokio::process::{Child, Command};
use crate::utils::LateInit;
static mut LOCK: Option<UnixListener> = None;
static LOCK: LateInit<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub fn entry() -> Result<()> {
pub async fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
check_permission()?;
ensure_single_instance()?;
spawn_daemon()
mount_prop().await?;
if check_and_set_hint()? == false {
log::warn!("Requirements not met, exiting");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
return Ok(());
}
let end = spawn_daemon().await;
set_prop_hint(constants::STATUS_CRASHED)?;
end
}
fn check_permission() -> Result<()> {
@@ -41,54 +58,138 @@ fn check_permission() -> Result<()> {
fn ensure_single_instance() -> Result<()> {
log::info!("Ensure single instance");
let name = String::from("zygiskwd") + constants::SOCKET_PLACEHOLDER;
let name = format!("zygiskwd{}", magic::MAGIC.as_str());
match utils::abstract_namespace_socket(&name) {
Ok(socket) => unsafe { LOCK = Some(socket) },
Ok(socket) => LOCK.init(socket),
Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?")
}
Ok(())
}
fn spawn_daemon() -> Result<()> {
async fn mount_prop() -> Result<()> {
let module_prop = if let root_impl::RootImpl::Magisk = root_impl::get_impl() {
let magisk_path = Command::new("magisk").arg("--path").output().await?;
let mut magisk_path = String::from_utf8(magisk_path.stdout)?;
magisk_path.pop(); // Removing '\n'
let cwd = std::env::current_dir()?;
let dir = cwd.file_name().unwrap().to_string_lossy();
format!("{magisk_path}/.magisk/modules/{dir}/{}", constants::PATH_MODULE_PROP)
} else {
constants::PATH_MODULE_PROP.to_string()
};
log::info!("Mount {module_prop}");
let module_prop_file = fs::File::open(&module_prop)?;
let mut section = 0;
let mut sections: [String; 2] = [String::new(), String::new()];
let lines = io::BufReader::new(module_prop_file).lines();
for line in lines {
let line = line?;
if line.starts_with("description=") {
sections[0].push_str("description=");
sections[1].push_str(line.trim_start_matches("description="));
sections[1].push('\n');
section = 1;
} else {
sections[section].push_str(&line);
sections[section].push('\n');
}
}
PROP_SECTIONS.init(sections);
fs::create_dir(magic::PATH_TMP_DIR.as_str())?;
fs::File::create(magic::PATH_TMP_PROP.as_str())?;
// FIXME: sys_mount cannot be compiled on 32 bit
unsafe {
let r = libc::mount(
CString::new(magic::PATH_TMP_PROP.as_str())?.as_ptr(),
CString::new(module_prop)?.as_ptr(),
std::ptr::null(),
libc::MS_BIND,
std::ptr::null(),
);
Errno::result(r)?;
}
Ok(())
}
fn set_prop_hint(hint: &str) -> Result<()> {
let mut file = fs::File::create(magic::PATH_TMP_PROP.as_str())?;
file.write_all(PROP_SECTIONS[0].as_bytes())?;
file.write_all(b"[")?;
file.write_all(hint.as_bytes())?;
file.write_all(b"] ")?;
file.write_all(PROP_SECTIONS[1].as_bytes())?;
Ok(())
}
fn check_and_set_hint() -> Result<bool> {
let root_impl = root_impl::get_impl();
match root_impl {
root_impl::RootImpl::None => set_prop_hint(constants::STATUS_ROOT_IMPL_NONE)?,
root_impl::RootImpl::TooOld => set_prop_hint(constants::STATUS_ROOT_IMPL_TOO_OLD)?,
root_impl::RootImpl::Abnormal => set_prop_hint(constants::STATUS_ROOT_IMPL_ABNORMAL)?,
root_impl::RootImpl::Multiple => set_prop_hint(constants::STATUS_ROOT_IMPL_MULTIPLE)?,
_ => {
set_prop_hint(constants::STATUS_LOADED)?;
return Ok(true);
}
}
Ok(false)
}
async fn spawn_daemon() -> Result<()> {
let mut lives = 5;
loop {
let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output=Result<()>>>>>::new();
let mut child_ids = vec![];
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let mut child_ids = vec![];
let (sender, receiver) = mpsc::channel();
let mut spawn = |mut daemon: Child| {
child_ids.push(daemon.id());
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
let _ = sender.send(());
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let mut binder = loop {
if receiver.try_recv().is_ok() {
bail!("Daemon died before system server ready");
}
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
thread::sleep(Duration::from_secs(1));
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if receiver.try_recv().is_ok() || binder.ping_binder().is_err() { break; }
thread::sleep(Duration::from_secs(1))
async fn spawn_daemon(mut daemon: Child) -> Result<()> {
let result = daemon.wait().await?;
log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result);
Ok(())
}
if let Ok(it) = daemon32 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
}
if let Ok(it) = daemon64 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
}
async fn binder_listener() -> Result<()> {
let mut binder = loop {
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
tokio::time::sleep(Duration::from_secs(1)).await;
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if binder.ping_binder().is_err() { break; }
tokio::time::sleep(Duration::from_secs(1)).await;
}
log::error!("System server died");
Ok(())
}
futures.push(Box::pin(binder_listener()));
if let Err(e) = futures.next().await.unwrap() {
log::error!("{}", e);
}
for child in child_ids {
log::debug!("Killing child process {}", child);
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
}
@@ -99,7 +200,6 @@ fn spawn_daemon() -> Result<()> {
log::error!("Restarting zygote...");
utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?;
utils::set_property(constants::PROP_SVC_ZYGOTE, "restart")?;
thread::sleep(Duration::from_secs(2));
utils::set_property(constants::PROP_CTL_RESTART, "zygote")?;
}
}

View File

@@ -1,29 +1,28 @@
use crate::constants::DaemonSocketAction;
use std::ffi::c_void;
use crate::constants::{DaemonSocketAction, ProcessFlags};
use crate::utils::UnixStreamExt;
use crate::{constants, lp_select, root_impl, utils};
use crate::{constants, dl, lp_select, magic, root_impl, utils};
use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
fcntl::{fcntl, FcntlArg, FdFlag},
libc::self,
};
use passfd::FdPassingExt;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::thread;
use std::ffi::c_char;
use std::fs;
use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::Command;
use nix::libc;
use nix::sys::stat::fstat;
use nix::unistd::close;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
struct Module {
name: String,
memfd: Memfd,
companion: Mutex<Option<UnixStream>>,
lib_fd: OwnedFd,
entry: Option<ZygiskCompanionEntryFn>,
}
struct Context {
@@ -64,8 +63,7 @@ pub fn entry() -> Result<()> {
}
fn get_arch() -> Result<&'static str> {
let output = Command::new("getprop").arg("ro.product.cpu.abi").output()?;
let system_arch = String::from_utf8(output.stdout)?;
let system_arch = utils::get_property("ro.product.cpu.abi")?;
if system_arch.contains("arm") {
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
}
@@ -77,15 +75,15 @@ fn get_arch() -> Result<&'static str> {
fn load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_MODULE_DIR) {
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
Ok(dir) => dir,
Err(e) => {
log::warn!("Failed reading modules directory: {}", e);
return Ok(modules);
}
};
for entry_result in dir.into_iter() {
let entry = entry_result?;
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");
@@ -93,30 +91,28 @@ fn load_modules(arch: &str) -> Result<Vec<Module>> {
continue;
}
log::info!(" Loading module `{name}`...");
let memfd = match create_memfd(&so_path) {
Ok(memfd) => memfd,
let lib_fd = match create_library_fd(&so_path) {
Ok(fd) => fd,
Err(e) => {
log::warn!(" Failed to create memfd for `{name}`: {e}");
continue;
}
};
let companion = match spawn_companion(&name, &memfd) {
Ok(companion) => companion,
Err(e) => {
log::warn!(" Failed to spawn companion for `{name}`: {e}");
continue;
}
};
let companion = Mutex::new(companion);
let module = Module { name, memfd, companion };
let entry = resolve_module(&so_path.to_string_lossy())?;
let module = Module { name, lib_fd, entry };
modules.push(module);
}
Ok(modules)
}
fn create_memfd(so_path: &PathBuf) -> Result<Memfd> {
#[cfg(debug_assertions)]
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
Ok(OwnedFd::from(fs::File::open(so_path)?))
}
#[cfg(not(debug_assertions))]
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
let opts = memfd::MemfdOptions::default().allow_sealing(true);
let memfd = opts.create("jit-cache")?;
let file = fs::File::open(so_path)?;
@@ -131,38 +127,28 @@ fn create_memfd(so_path: &PathBuf) -> Result<Memfd> {
seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?;
Ok(memfd)
Ok(OwnedFd::from(memfd.into_file()))
}
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let prefix = lp_select!("zygiskd32", "zygiskd64");
let name = String::from(prefix) + constants::SOCKET_PLACEHOLDER;
let name = format!("{}{}", prefix, magic::MAGIC.as_str());
let listener = utils::abstract_namespace_socket(&name)?;
log::debug!("Daemon socket: {name}");
Ok(listener)
}
fn spawn_companion(name: &str, memfd: &Memfd) -> Result<Option<UnixStream>> {
let (mut daemon, companion) = UnixStream::pair()?;
// Remove FD_CLOEXEC flag
fcntl(companion.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?;
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
Command::new(&process)
.arg0(format!("{}-{}", nice_name, name))
.arg("companion")
.arg(format!("{}", companion.as_raw_fd()))
.spawn()?;
drop(companion);
daemon.write_string(name)?;
daemon.send_fd(memfd.as_raw_fd())?;
match daemon.read_u8()? {
0 => Ok(None),
1 => Ok(Some(daemon)),
_ => bail!("Invalid companion response"),
fn resolve_module(path: &str) -> Result<Option<ZygiskCompanionEntryFn>> {
unsafe {
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))
}
}
@@ -181,12 +167,8 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
Err(_) => break,
};
let tag = stream.read_string()?;
let tag = std::ffi::CString::new(tag)?;
let message = stream.read_string()?;
let message = std::ffi::CString::new(message)?;
unsafe {
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
}
utils::log_raw(level as i32, &tag, &message)?;
}
}
DaemonSocketAction::ReadNativeBridge => {
@@ -194,52 +176,61 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
}
DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32;
let mut flags = 0u32;
if root_impl::uid_on_allowlist(uid) {
flags |= constants::PROCESS_GRANTED_ROOT;
let mut flags = ProcessFlags::empty();
if root_impl::uid_granted_root(uid) {
flags |= ProcessFlags::PROCESS_GRANTED_ROOT;
}
if root_impl::uid_on_denylist(uid) {
flags |= constants::PROCESS_ON_DENYLIST;
if root_impl::uid_should_umount(uid) {
flags |= ProcessFlags::PROCESS_ON_DENYLIST;
}
// TODO: PROCESS_IS_SYSUI?
stream.write_u32(flags)?;
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,
_ => unreachable!(),
}
log::trace!("Uid {} granted root: {}", uid, flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT));
log::trace!("Uid {} on denylist: {}", uid, flags.contains(ProcessFlags::PROCESS_ON_DENYLIST));
stream.write_u32(flags.bits())?;
}
DaemonSocketAction::ReadModules => {
stream.write_usize(context.modules.len())?;
for module in context.modules.iter() {
stream.write_string(&module.name)?;
stream.send_fd(module.memfd.as_raw_fd())?;
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();
match companion.as_ref() {
Some(sock) => {
if let Err(_) = sock.send_fd(stream.as_raw_fd()) {
log::error!("Companion of module `{}` crashed", module.name);
companion.take();
stream.write_u8(0)?;
}
// Ok: Send by companion
}
match module.entry {
None => {
stream.write_u8(0)?;
return Ok(());
}
Some(companion) => {
stream.write_u8(1)?;
let fd = stream.into_raw_fd();
let st0 = fstat(fd)?;
unsafe { companion(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(fd) {
if st0.st_dev == st1.st_dev && st0.st_ino == st1.st_ino {
close(fd)?;
}
}
}
}
}
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_MODULE_DIR, module.name);
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
}
Ok(())
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
}