Compare commits

...

7 Commits

Author SHA1 Message Date
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
23 changed files with 276 additions and 52 deletions

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/*"

View File

@@ -31,7 +31,7 @@ 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.6.2")
val verName by extra("v4-0.6.4")
val verCode by extra(gitCommitCount)
val minKsuVersion by extra(10654)
val minKsudVersion by extra(10670)

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

@@ -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);
}

View File

@@ -16,5 +16,6 @@ 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)

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;

View File

@@ -331,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

@@ -146,20 +146,9 @@ else
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/libzygisk_injector.so"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib/libzygisk_loader.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/libzygisk_injector.so"
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib64/libzygisk_loader.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

@@ -14,3 +14,4 @@ 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

@@ -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

@@ -19,15 +19,12 @@ pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
pub const PROP_CTL_RESTART: &str = "ctl.restart";
pub const ZYGISK_LOADER: &str = "libzygisk_loader.so";
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
pub const ZYGISK_MAGIC: &str = "/system/zygisk_magic";
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 PATH_TMP_DIR: &str = concatcp!("/dev/", SOCKET_PLACEHOLDER);
pub const PATH_TMP_PROP: &str = concatcp!("/dev/", SOCKET_PLACEHOLDER, "/module.prop");
pub const STATUS_LOADED: &str = "😋 Zygisksu is loaded";
pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed";

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

@@ -3,6 +3,7 @@
mod companion;
mod constants;
mod dl;
mod magic;
mod root_impl;
mod utils;
mod watchdog;
@@ -39,6 +40,7 @@ fn init_android_logger(tag: &str) {
fn start() -> Result<()> {
root_impl::setup();
magic::setup()?;
let cli = Args::parse();
match cli.command {
Commands::Watchdog => watchdog::entry()?,

View File

@@ -24,6 +24,7 @@ pub fn get_kernel_su() -> Option<Version> {
}
}
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];
@@ -32,6 +33,7 @@ pub fn uid_on_allowlist(uid: i32) -> bool {
uids.contains(&uid)
}
#[inline(never)]
pub fn uid_on_denylist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];

View File

@@ -23,6 +23,7 @@ pub fn get_magisk() -> Option<Version> {
})
}
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
let output: Option<String> = Command::new("magisk")
.arg("--sqlite")
@@ -40,6 +41,7 @@ pub fn uid_on_allowlist(uid: i32) -> bool {
})
}
#[inline(never)]
pub fn uid_on_denylist(uid: i32) -> bool {
// TODO: uid_on_denylist
return false;

View File

@@ -5,6 +5,7 @@ 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")]
@@ -18,6 +19,27 @@ macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp32 };
}
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)
}

View File

@@ -1,4 +1,4 @@
use crate::{constants, root_impl, utils};
use crate::{constants, magic, root_impl, utils};
use anyhow::{bail, Result};
use nix::unistd::{getgid, getuid, Pid};
use std::process::{Child, Command};
@@ -12,10 +12,10 @@ use binder::IBinder;
use nix::errno::Errno;
use nix::libc;
use nix::sys::signal::{kill, Signal};
use once_cell::sync::OnceCell;
use crate::utils::LateInit;
static LOCK: OnceCell<UnixListener> = OnceCell::new();
static PROP_SECTIONS: OnceCell<[String; 2]> = OnceCell::new();
static LOCK: LateInit<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
@@ -55,9 +55,9 @@ 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) => { let _ = LOCK.set(socket); }
Ok(socket) => LOCK.init(socket),
Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?")
}
Ok(())
@@ -91,15 +91,15 @@ fn mount_prop() -> Result<()> {
sections[section].push('\n');
}
}
let _ = PROP_SECTIONS.set(sections);
PROP_SECTIONS.init(sections);
fs::create_dir(constants::PATH_TMP_DIR)?;
fs::File::create(constants::PATH_TMP_PROP)?;
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(constants::PATH_TMP_PROP)?.as_ptr(),
CString::new(magic::PATH_TMP_PROP.as_str())?.as_ptr(),
CString::new(module_prop)?.as_ptr(),
std::ptr::null(),
libc::MS_BIND,
@@ -112,13 +112,12 @@ fn mount_prop() -> Result<()> {
}
fn set_prop_hint(hint: &str) -> Result<()> {
let mut file = fs::File::create(constants::PATH_TMP_PROP)?;
let sections = PROP_SECTIONS.get().unwrap();
file.write_all(sections[0].as_bytes())?;
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(sections[1].as_bytes())?;
file.write_all(PROP_SECTIONS[1].as_bytes())?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
use crate::constants::DaemonSocketAction;
use crate::utils::UnixStreamExt;
use crate::{constants, lp_select, root_impl, utils};
use crate::{constants, lp_select, magic, root_impl, utils};
use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
@@ -135,7 +135,7 @@ fn create_memfd(so_path: &PathBuf) -> Result<Memfd> {
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)
@@ -198,7 +198,7 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= constants::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= constants::PROCESS_ROOT_IS_MAGISK,
_ => ()
_ => unreachable!(),
}
// TODO: PROCESS_IS_SYSUI?
stream.write_u32(flags)?;