Updated to 4.0.0

This commit is contained in:
Fr4nz D13trich 2025-11-20 21:24:53 +01:00
parent b7554a5383
commit 938198bf11
234 changed files with 21069 additions and 12710 deletions

View file

@ -1,2 +0,0 @@
/obj
/libs

View file

@ -1,6 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := kpmmgr
LOCAL_SRC_FILES := kpmmgr.c
include $(BUILD_EXECUTABLE)

View file

@ -1,3 +0,0 @@
APP_ABI := arm64-v8a
APP_PLATFORM := android-24
APP_STL := none

View file

@ -1,118 +0,0 @@
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <string.h>
#include <errno.h>
#define KERNEL_SU_OPTION 0xDEADBEEF
#define KSU_OPTIONS 0xdeadbeef
// KPM控制代码
#define CMD_KPM_CONTROL 28
#define CMD_KPM_CONTROL_MAX 7
// 控制代码
// prctl(xxx, 28, "PATH", "ARGS")
// success return 0, error return -N
#define SUKISU_KPM_LOAD 28
// prctl(xxx, 29, "NAME")
// success return 0, error return -N
#define SUKISU_KPM_UNLOAD 29
// num = prctl(xxx, 30)
// error return -N
// success return +num or 0
#define SUKISU_KPM_NUM 30
// prctl(xxx, 31, Buffer, BufferSize)
// success return +out, error return -N
#define SUKISU_KPM_LIST 31
// prctl(xxx, 32, "NAME", Buffer[256])
// success return +out, error return -N
#define SUKISU_KPM_INFO 32
// prctl(xxx, 33, "NAME", "ARGS")
// success return KPM's result value
// error return -N
#define SUKISU_KPM_CONTROL 33
// prctl(xxx, 34, buffer, bufferSize)
// success return KPM's result value
// error return -N
#define SUKISU_KPM_VERSION 34
#define CONTROL_CODE(n) (n)
void print_usage(const char *prog) {
printf("Usage: %s <command> [args]\n", prog);
printf("Commands:\n");
printf(" load <path> <args> Load a KPM module\n");
printf(" unload <name> Unload a KPM module\n");
printf(" num Get number of loaded modules\n");
printf(" list List loaded KPM modules\n");
printf(" info <name> Get info of a KPM module\n");
printf(" control <name> <args> Send control command to a KPM module\n");
printf(" version Print KPM Loader version\n");
}
int main(int argc, char *argv[]) {
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
int ret = -1;
int out = -1; // 存储返回值
if (strcmp(argv[1], "load") == 0 && argc >= 3) {
// 加载 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LOAD), argv[2], (argc > 3 ? argv[3] : NULL), &out);
if(out > 0) {
printf("Success");
}
} else if (strcmp(argv[1], "unload") == 0 && argc >= 3) {
// 卸载 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_UNLOAD), argv[2], NULL, &out);
} else if (strcmp(argv[1], "num") == 0) {
// 获取加载的 KPM 数量
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_NUM), NULL, NULL, &out);
printf("%d", out);
return 0;
} else if (strcmp(argv[1], "list") == 0) {
// 获取模块列表
char buffer[1024] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_LIST), buffer, sizeof(buffer), &out);
if (out >= 0) {
printf("%s", buffer);
}
} else if (strcmp(argv[1], "info") == 0 && argc >= 3) {
// 获取指定模块信息
char buffer[256] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_INFO), argv[2], buffer, &out);
if (out >= 0) {
printf("%s\n", buffer);
}
} else if (strcmp(argv[1], "control") == 0 && argc >= 4) {
// 控制 KPM 模块
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_CONTROL), argv[2], argv[3], &out);
} else if (strcmp(argv[1], "version") == 0) {
char buffer[1024] = {0};
ret = prctl(KSU_OPTIONS, CONTROL_CODE(SUKISU_KPM_VERSION), buffer, sizeof(buffer), &out);
if (out >= 0) {
printf("%s", buffer);
}
} else {
print_usage(argv[0]);
return 1;
}
if (out < 0) {
printf("Error: %s\n", strerror(-out));
return -1;
}
return 0;
}

View file

@ -594,16 +594,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fs4"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4"
dependencies = [
"rustix 1.0.7",
"windows-sys 0.59.0",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@ -835,6 +825,42 @@ dependencies = [
"libc",
]
[[package]]
name = "ksud"
version = "0.1.0"
dependencies = [
"android-properties",
"android_logger",
"anyhow",
"chrono",
"clap",
"const_format",
"derive-new",
"encoding_rs",
"env_logger",
"extattr",
"getopts",
"humansize",
"is_executable",
"java-properties",
"jwalk",
"libc",
"log",
"nom",
"notify",
"regex-lite",
"rust-embed",
"rustix 0.38.34",
"serde",
"serde_json",
"sha1",
"sha256",
"tempfile",
"which",
"zip",
"zip-extensions",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -1048,31 +1074,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "procfs"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
dependencies = [
"bitflags 2.8.0",
"chrono",
"flate2",
"hex",
"procfs-core",
"rustix 0.38.44",
]
[[package]]
name = "procfs-core"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
dependencies = [
"bitflags 2.8.0",
"chrono",
"hex",
]
[[package]]
name = "quote"
version = "1.0.40"
@ -1184,19 +1185,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.8.0",
"errno 0.3.10",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.7"
@ -1837,43 +1825,6 @@ dependencies = [
"lzma-sys",
]
[[package]]
name = "zakozako"
version = "0.1.0"
dependencies = [
"android-properties",
"android_logger",
"anyhow",
"chrono",
"clap",
"const_format",
"derive-new",
"encoding_rs",
"env_logger",
"extattr",
"fs4",
"getopts",
"humansize",
"is_executable",
"java-properties",
"jwalk",
"libc",
"log",
"nom",
"notify",
"procfs",
"regex-lite",
"rust-embed",
"rustix 0.38.34",
"serde_json",
"sha1",
"sha256",
"tempfile",
"which",
"zip",
"zip-extensions",
]
[[package]]
name = "zerocopy"
version = "0.8.25"

View file

@ -1,5 +1,5 @@
[package]
name = "zakozako"
name = "ksud"
version = "0.1.0"
edition = "2024"
@ -45,7 +45,7 @@ sha1 = "0.10"
tempfile = "3"
chrono = "0.4"
regex-lite = "0.1"
fs4 = "0.13"
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies]
rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", features = [
@ -53,7 +53,6 @@ rustix = { git = "https://github.com/Kernel-SU/rustix.git", branch = "main", fea
] }
# some android specific dependencies which compiles under unix are also listed here for convenience of coding
android-properties = { version = "0.2", features = ["bionic-deprecated"] }
procfs = "0.17"
[target.'cfg(target_os = "android")'.dependencies]
android_logger = { version = "0.14", default-features = false }
@ -64,3 +63,4 @@ codegen-units = 1
lto = "fat"
opt-level = 3
strip = true
split-debuginfo = "unpacked"

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,4 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::{env, fs::File, io::Write, path::Path, process::Command};
fn get_git_version() -> Result<(u32, String), std::io::Error> {
let output = Command::new("git")
@ -14,8 +10,8 @@ fn get_git_version() -> Result<(u32, String), std::io::Error> {
let version_code: u32 = version_code
.trim()
.parse()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to parse git count"))?;
let version_code = 10000 + 700 + version_code; // For historical reasons
.map_err(|_| std::io::Error::other("Failed to parse git count"))?;
let version_code = 40000 - 2815 + version_code; // For historical reasons
let version_name = String::from_utf8(
Command::new("git")
@ -23,7 +19,7 @@ fn get_git_version() -> Result<(u32, String), std::io::Error> {
.output()?
.stdout,
)
.map_err(|_| std::io::Error::other("Failed to read git describe stdout"))?;
.map_err(|_| std::io::Error::other("Failed to parse git count"))?;
let version_name = version_name.trim_start_matches('v').to_string();
Ok((version_code, version_name))
}

View file

@ -1,22 +1,19 @@
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use std::{
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use anyhow::{Context, Result, anyhow, bail, ensure};
use regex_lite::Regex;
use which::which;
use crate::defs;
use crate::defs::BACKUP_FILENAME;
use crate::defs::{KSU_BACKUP_DIR, KSU_BACKUP_FILE_PREFIX};
use crate::{assets, utils};
use crate::{
assets,
defs::{self, BACKUP_FILENAME, KSU_BACKUP_DIR, KSU_BACKUP_FILE_PREFIX},
utils,
};
#[cfg(target_os = "android")]
fn ensure_gki_kernel() -> Result<()> {
@ -27,7 +24,7 @@ fn ensure_gki_kernel() -> Result<()> {
}
#[cfg(target_os = "android")]
pub fn get_kernel_version() -> Result<(i32, i32, i32)> {
fn get_kernel_version() -> Result<(i32, i32, i32)> {
let uname = rustix::system::uname();
let version = uname.release().to_string_lossy();
let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?;
@ -118,11 +115,11 @@ fn parse_kmi_from_kernel(kernel: &PathBuf, workdir: &Path) -> Result<String> {
let re =
Regex::new(r"(?:.* )?(\d+\.\d+)(?:\S+)?(android\d+)").context("Failed to compile regex")?;
for s in printable_strings {
if let Some(caps) = re.captures(s) {
if let (Some(kernel_version), Some(android_version)) = (caps.get(1), caps.get(2)) {
let kmi = format!("{}-{}", android_version.as_str(), kernel_version.as_str());
return Ok(kmi);
}
if let Some(caps) = re.captures(s)
&& let (Some(kernel_version), Some(android_version)) = (caps.get(1), caps.get(2))
{
let kmi = format!("{}-{}", android_version.as_str(), kernel_version.as_str());
return Ok(kmi);
}
}
println!("- Failed to get KMI version");
@ -153,126 +150,40 @@ fn parse_kmi_from_boot(magiskboot: &Path, image: &PathBuf, workdir: &Path) -> Re
parse_kmi_from_kernel(&image_path, workdir)
}
fn do_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
fn do_cpio_cmd(magiskboot: &Path, workdir: &Path, cpio_path: &Path, cmd: &str) -> Result<()> {
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("cpio")
.arg("ramdisk.cpio")
.arg(cpio_path)
.arg(cmd)
.status()?;
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
Ok(())
}
fn do_vendor_init_boot_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
let vendor_init_boot_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
fn is_magisk_patched(magiskboot: &Path, workdir: &Path, cpio_path: &Path) -> Result<bool> {
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("cpio")
.arg(vendor_init_boot_cpio)
.arg(cmd)
.arg(cpio_path)
.arg("test")
.status()?;
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
Ok(())
// 0: stock, 1: magisk
Ok(status.code() == Some(1))
}
fn do_vendor_ramdisk_cpio_cmd(magiskboot: &Path, workdir: &Path, cmd: &str) -> Result<()> {
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
fn is_kernelsu_patched(magiskboot: &Path, workdir: &Path, cpio_path: &Path) -> Result<bool> {
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("cpio")
.arg(vendor_ramdisk_cpio)
.arg(cmd)
.status()?;
ensure!(status.success(), "magiskboot cpio {} failed", cmd);
Ok(())
}
fn is_magisk_patched(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(["cpio", "ramdisk.cpio", "test"])
.status()?;
// 0: stock, 1: magisk
Ok(status.code() == Some(1))
}
fn is_magisk_patched_vendor_init_boot(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let vendor_init_boot_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(["cpio", vendor_init_boot_cpio.to_str().unwrap(), "test"])
.status()?;
// 0: stock, 1: magisk
Ok(status.code() == Some(1))
}
fn is_magisk_patched_vendor_ramdisk(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(["cpio", vendor_ramdisk_cpio.to_str().unwrap(), "test"])
.status()?;
// 0: stock, 1: magisk
Ok(status.code() == Some(1))
}
fn is_kernelsu_patched(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args(["cpio", "ramdisk.cpio", "exists kernelsu.ko"])
.status()?;
Ok(status.success())
}
fn is_kernelsu_patched_vendor_init_boot(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("init_boot.cpio");
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args([
"cpio",
vendor_ramdisk_cpio.to_str().unwrap(),
"exists kernelsu.ko",
])
.status()?;
Ok(status.success())
}
fn is_kernelsu_patched_vendor_ramdisk(magiskboot: &Path, workdir: &Path) -> Result<bool> {
let vendor_ramdisk_cpio = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.args([
"cpio",
vendor_ramdisk_cpio.to_str().unwrap(),
"exists kernelsu.ko",
])
.arg(cpio_path)
.arg("exists kernelsu.ko")
.status()?;
Ok(status.success())
@ -308,10 +219,7 @@ pub fn restore(
let kmi = get_current_kmi().unwrap_or_else(|_| String::from(""));
let skip_init = kmi.starts_with("android12-");
let (bootimage, bootdevice) =
find_boot_image(&image, skip_init, false, false, workdir, &magiskboot)?;
let (bootimage, bootdevice) = find_boot_image(&image, &kmi, false, false, workdir, &None)?;
println!("- Unpacking boot image");
let status = Command::new(&magiskboot)
@ -323,33 +231,36 @@ pub fn restore(
.status()?;
ensure!(status.success(), "magiskboot unpack failed");
let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
// let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
let no_vendor_init_boot = !workdir
.join("vendor_ramdisk")
.join("init_boot.cpio")
.exists();
let no_vendor_ramdisk = !workdir.join("vendor_ramdisk").join("ramdisk.cpio").exists();
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?;
let is_kernelsu_patched_vendor_init_boot =
is_kernelsu_patched_vendor_init_boot(&magiskboot, workdir)?;
let is_kernelsu_patched_vendor_ramdisk =
is_kernelsu_patched_vendor_ramdisk(&magiskboot, workdir)?;
ensure!(
is_kernelsu_patched
|| is_kernelsu_patched_vendor_init_boot
|| is_kernelsu_patched_vendor_ramdisk,
"boot image is not patched by KernelSU"
);
let mut ramdisk = workdir.join("ramdisk.cpio");
if !ramdisk.exists() {
ramdisk = workdir.join("vendor_ramdisk").join("init_boot.cpio")
}
if !ramdisk.exists() {
ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
}
if !ramdisk.exists() {
bail!("No compatible ramdisk found.")
}
let ramdisk = ramdisk.as_path();
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir, ramdisk)?;
ensure!(is_kernelsu_patched, "boot image is not patched by KernelSU");
let mut new_boot = None;
let mut from_backup = false;
#[cfg(target_os = "android")]
if do_cpio_cmd(&magiskboot, workdir, &format!("exists {BACKUP_FILENAME}")).is_ok() {
if do_cpio_cmd(
&magiskboot,
workdir,
ramdisk,
&format!("exists {BACKUP_FILENAME}"),
)
.is_ok()
{
do_cpio_cmd(
&magiskboot,
workdir,
ramdisk,
&format!("extract {BACKUP_FILENAME} {BACKUP_FILENAME}"),
)?;
let sha = std::fs::read(workdir.join(BACKUP_FILENAME))?;
@ -372,81 +283,13 @@ pub fn restore(
}
if new_boot.is_none() {
if !no_ramdisk {
println!("- Restoring /ramdisk");
println!("- Removing /ramdisk/kernelsu.ko");
// remove kernelsu.ko
do_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
// remove kernelsu.ko
do_cpio_cmd(&magiskboot, workdir, ramdisk, "rm kernelsu.ko")?;
// if init.real exists, restore it
println!("- Checking if init.real exists");
let status = do_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
if status {
println!("- /ramdisk/init.real exists");
println!("- Restoring /ramdisk/init.real to init");
do_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
} else {
println!("- /ramdisk/init.real not found");
println!("- Removing ramdisk.cpio");
let ramdisk = workdir.join("ramdisk.cpio");
std::fs::remove_file(ramdisk)?;
}
} else if !no_vendor_init_boot {
println!("- Restoring /vendor_ramdisk/init_boot");
println!("- Removing /vendor_ramdisk/init_boot/kernelsu.ko");
// vendor init_boot restore
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
println!("- Checking if init.real exists");
let status =
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
if status {
println!("- /vendor_ramdisk/init_boot/init.real exists");
println!("- Restoring /vendor_ramdisk/init_boot/init.real to init");
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
} else {
println!("- /vendor_ramdisk/init_boot/init.real not found");
println!("- Removing vendor_ramdisk/init_boot.cpio");
let vendor_init_boot = workdir.join("vendor_ramdisk").join("init_boot.cpio");
std::fs::remove_file(vendor_init_boot)?;
}
} else if !no_vendor_ramdisk {
println!("- Restoring /vendor_ramdisk/ramdisk");
println!("- Removing /vendor_ramdisk/ramdisk/kernelsu.ko");
// vendor ramdisk restore
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
let status =
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
if status {
println!("- /vendor_ramdisk/ramdisk/init.real exists");
println!("- Restoring /vendor_ramdisk/ramdisk/init.real to init");
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
} else {
println!("- /vendor_ramdisk/ramdisk/init.real not found");
println!("- Removing vendor_ramdisk/ramdisk.cpio");
let vendor_ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
std::fs::remove_file(vendor_ramdisk)?;
}
} else {
println!("- Restoring /ramdisk");
println!("- Removing /ramdisk/kernelsu.ko");
// remove kernelsu.ko
do_cpio_cmd(&magiskboot, workdir, "rm kernelsu.ko")?;
// if init.real exists, restore it
println!("- Checking if init.real exists");
let status = do_cpio_cmd(&magiskboot, workdir, "exists init.real").is_ok();
if status {
println!("- /ramdisk/init.real exists");
println!("- Restoring /ramdisk/init.real to init");
do_cpio_cmd(&magiskboot, workdir, "mv init.real init")?;
} else {
println!("- /ramdisk/init.real not found");
println!("- Removing ramdisk.cpio");
let ramdisk = workdir.join("ramdisk.cpio");
std::fs::remove_file(ramdisk)?;
}
// if init.real exists, restore it
let status = do_cpio_cmd(&magiskboot, workdir, ramdisk, "exists init.real").is_ok();
if status {
do_cpio_cmd(&magiskboot, workdir, ramdisk, "mv init.real init")?;
}
println!("- Repacking boot image");
@ -455,7 +298,7 @@ pub fn restore(
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("repack")
.arg(bootimage.display().to_string())
.arg(&bootimage)
.status()?;
ensure!(status.success(), "magiskboot repack failed");
new_boot = Some(workdir.join("new-boot.img"));
@ -501,8 +344,11 @@ pub fn patch(
out: Option<PathBuf>,
magiskboot: Option<PathBuf>,
kmi: Option<String>,
partition: Option<String>,
) -> Result<()> {
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot, kmi);
let result = do_patch(
image, kernel, kmod, init, ota, flash, out, magiskboot, kmi, partition,
);
if let Err(ref e) = result {
println!("- Install Error: {e}");
}
@ -520,6 +366,7 @@ fn do_patch(
out: Option<PathBuf>,
magiskboot_path: Option<PathBuf>,
kmi: Option<String>,
partition: Option<String>,
) -> Result<()> {
println!(include_str!("banner"));
@ -574,18 +421,10 @@ fn do_patch(
}
};
let skip_init = kmi.starts_with("android12-");
let (bootimage, bootdevice) =
find_boot_image(&image, &kmi, ota, is_replace_kernel, workdir, &partition)?;
let (bootimage, bootdevice) = find_boot_image(
&image,
skip_init,
ota,
is_replace_kernel,
workdir,
&magiskboot,
)?;
let bootimage = bootimage.display().to_string();
let bootimage = bootimage.as_path();
// try extract magiskboot/bootctl
let _ = assets::ensure_binaries(false);
@ -620,101 +459,49 @@ fn do_patch(
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("unpack")
.arg(&bootimage)
.arg(bootimage)
.status()?;
ensure!(status.success(), "magiskboot unpack failed");
let no_ramdisk = !workdir.join("ramdisk.cpio").exists();
let no_vendor_init_boot = !workdir
.join("vendor_ramdisk")
.join("init_boot.cpio")
.exists();
let no_vendor_ramdisk = !workdir.join("vendor_ramdisk").join("ramdisk.cpio").exists();
if no_ramdisk && no_vendor_init_boot && no_vendor_ramdisk {
println!("- No compatible ramdisk found.");
println!("- Will create our own ramdisk!");
let mut ramdisk = workdir.join("ramdisk.cpio");
if !ramdisk.exists() {
ramdisk = workdir.join("vendor_ramdisk").join("init_boot.cpio")
}
let is_magisk_patched = is_magisk_patched(&magiskboot, workdir)?;
let is_magisk_patched_vendor_init_boot =
is_magisk_patched_vendor_init_boot(&magiskboot, workdir)?;
let is_magisk_patched_vendor_ramdisk = is_magisk_patched_vendor_ramdisk(&magiskboot, workdir)?;
ensure!(
!is_magisk_patched
|| !is_magisk_patched_vendor_init_boot
|| !is_magisk_patched_vendor_ramdisk,
"Cannot work with Magisk patched image"
);
if !ramdisk.exists() {
ramdisk = workdir.join("vendor_ramdisk").join("ramdisk.cpio");
}
if !ramdisk.exists() {
println!("- No ramdisk, create by default");
ramdisk = "ramdisk.cpio".into();
}
let ramdisk = ramdisk.as_path();
let is_magisk_patched = is_magisk_patched(&magiskboot, workdir, ramdisk)?;
ensure!(!is_magisk_patched, "Cannot work with Magisk patched image");
println!("- Adding KernelSU LKM");
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir)?;
let is_kernelsu_patched_vendor_init_boot =
is_kernelsu_patched_vendor_init_boot(&magiskboot, workdir)?;
let is_kernelsu_patched_vendor_ramdisk =
is_kernelsu_patched_vendor_ramdisk(&magiskboot, workdir)?;
let is_kernelsu_patched = is_kernelsu_patched(&magiskboot, workdir, ramdisk)?;
let mut need_backup = false;
if (no_ramdisk && !is_kernelsu_patched_vendor_init_boot)
|| (no_ramdisk && no_vendor_init_boot && !is_kernelsu_patched_vendor_ramdisk)
|| !is_kernelsu_patched
{
if !no_ramdisk {
println!("- Checking if /ramdisk/init exists");
let status = do_cpio_cmd(&magiskboot, workdir, "exists init");
if status.is_ok() {
println!("- Backing up ramdisk/init");
do_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
}
need_backup = flash;
} else if !no_vendor_init_boot {
println!("- Checking if /vendor_ramdisk/init_boot/init exists");
let status = do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "exists init");
if status.is_ok() {
println!("- Backing up vendor_ramdisk/init_boot/init");
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
}
need_backup = flash;
} else if !no_vendor_ramdisk {
println!("- Checking if /vendor_ramdisk/ramdisk/init exists");
let status = do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "exists init");
if status.is_ok() {
println!("- Backing up vendor_ramdisk/ramdisk/init");
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
}
need_backup = flash;
} else {
println!("- Checking if /ramdisk/init exists");
let status = do_cpio_cmd(&magiskboot, workdir, "exists init");
if status.is_ok() {
println!("- Backing up ramdisk/init");
do_cpio_cmd(&magiskboot, workdir, "mv init init.real")?;
}
need_backup = flash;
if !is_kernelsu_patched {
// kernelsu.ko is not exist, backup init if necessary
let status = do_cpio_cmd(&magiskboot, workdir, ramdisk, "exists init");
if status.is_ok() {
do_cpio_cmd(&magiskboot, workdir, ramdisk, "mv init init.real")?;
}
need_backup = flash;
}
if !no_ramdisk {
println!("- Patching /ramdisk");
do_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
do_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
} else if !no_vendor_init_boot {
println!("- Patching /vendor_ramdisk/init_boot");
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
do_vendor_init_boot_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
} else if !no_vendor_ramdisk {
println!("- Patching /vendor_ramdisk/ramdisk");
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "add 0750 init init")?;
do_vendor_ramdisk_cpio_cmd(&magiskboot, workdir, "add 0750 kernelsu.ko kernelsu.ko")?;
} else {
println!("- Creating and Patching /ramdisk");
do_cpio_cmd(&magiskboot, workdir, "add 0755 init init")?;
do_cpio_cmd(&magiskboot, workdir, "add 0755 kernelsu.ko kernelsu.ko")?;
}
do_cpio_cmd(&magiskboot, workdir, ramdisk, "add 0755 init init")?;
do_cpio_cmd(
&magiskboot,
workdir,
ramdisk,
"add 0755 kernelsu.ko kernelsu.ko",
)?;
#[cfg(target_os = "android")]
if need_backup {
if let Err(e) = do_backup(&magiskboot, workdir, &bootimage) {
println!("- Backup stock image failed: {e}");
}
if need_backup && let Err(e) = do_backup(&magiskboot, workdir, ramdisk, bootimage) {
println!("- Backup stock image failed: {e}");
}
println!("- Repacking boot image");
@ -724,7 +511,7 @@ fn do_patch(
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("repack")
.arg(&bootimage)
.arg(bootimage)
.status()?;
ensure!(status.success(), "magiskboot repack failed");
let new_boot = workdir.join("new-boot.img");
@ -779,7 +566,7 @@ fn calculate_sha1(file_path: impl AsRef<Path>) -> Result<String> {
}
#[cfg(target_os = "android")]
fn do_backup(magiskboot: &Path, workdir: &Path, image: &str) -> Result<()> {
fn do_backup(magiskboot: &Path, workdir: &Path, cpio_path: &Path, image: &Path) -> Result<()> {
let sha1 = calculate_sha1(image)?;
let filename = format!("{KSU_BACKUP_FILE_PREFIX}{sha1}");
@ -791,6 +578,7 @@ fn do_backup(magiskboot: &Path, workdir: &Path, image: &str) -> Result<()> {
do_cpio_cmd(
magiskboot,
workdir,
cpio_path,
&format!("add 0755 {BACKUP_FILENAME} {BACKUP_FILENAME}"),
)?;
println!("- Stock image has been backup to");
@ -860,143 +648,109 @@ fn find_magiskboot(magiskboot_path: Option<PathBuf>, workdir: &Path) -> Result<P
fn find_boot_image(
image: &Option<PathBuf>,
skip_init: bool,
kmi: &str,
ota: bool,
is_replace_kernel: bool,
workdir: &Path,
magiskboot: &Path,
partition: &Option<String>,
) -> Result<(PathBuf, Option<String>)> {
let bootimage;
let mut bootdevice = None;
if let Some(ref image) = *image {
ensure!(image.exists(), "- Boot image not found");
ensure!(image.exists(), "boot image not found");
bootimage = std::fs::canonicalize(image)?;
} else {
if cfg!(not(target_os = "android")) {
println!("- Current OS is not android, refusing auto bootimage/bootdevice detection");
bail!("- Please specify a boot image");
}
let mut slot_suffix =
utils::getprop("ro.boot.slot_suffix").unwrap_or_else(|| String::from(""));
if !slot_suffix.is_empty() && ota {
if slot_suffix == "_a" {
slot_suffix = "_b".to_string()
} else {
slot_suffix = "_a".to_string()
}
};
let init_boot_partition = format!("/dev/block/by-name/init_boot{slot_suffix}");
let vendor_boot_partition = format!("/dev/block/by-name/vendor_boot{slot_suffix}");
let boot_partition = format!("/dev/block/by-name/boot{slot_suffix}");
let init_boot_exist = Path::new(&init_boot_partition).exists();
let vendor_boot_exist = Path::new(&vendor_boot_partition).exists();
// helper: unpack a partition and check for a ramdisk and init
fn unpack_and_check_init(
magiskboot: &Path,
workdir: &Path,
partition: &str,
ramdisk_cpio: &str,
) -> Result<bool> {
let tmp_img = workdir.join("probe.img");
dd(partition, &tmp_img)?;
let status = Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("unpack")
.arg(&tmp_img)
.status()?;
if !status.success() {
let _ = std::fs::remove_file(&tmp_img);
return Ok(false);
}
let ramdisk_path = workdir.join(ramdisk_cpio);
let has_init = if ramdisk_path.exists() {
Command::new(magiskboot)
.current_dir(workdir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("cpio")
.arg(ramdisk_cpio)
.arg("exists init")
.status()
.map(|s| s.success())
.unwrap_or(false)
} else {
false
};
// Clean up
let _ = std::fs::remove_file(&tmp_img);
let _ = std::fs::remove_file(workdir.join("ramdisk.cpio"));
let _ = std::fs::remove_dir_all(workdir.join("vendor_ramdisk"));
Ok(has_init)
bail!("Please specify a boot image");
}
let mut selected_partition = &boot_partition;
let slot_suffix = get_slot_suffix(ota);
let boot_partition_name = choose_boot_partition(kmi, is_replace_kernel, partition);
let boot_partition = format!("/dev/block/by-name/{boot_partition_name}{slot_suffix}");
if !is_replace_kernel && init_boot_exist && !skip_init {
// try init_boot/ramdisk.cpio
if unpack_and_check_init(magiskboot, workdir, &init_boot_partition, "ramdisk.cpio")? {
println!("- Using init_boot partition (ramdisk.cpio).");
selected_partition = &init_boot_partition;
}
}
// try vendor_boot/vendor_ramdisk/init_boot.cpio
if selected_partition == &boot_partition
&& !is_replace_kernel
&& vendor_boot_exist
&& !skip_init
{
if unpack_and_check_init(
magiskboot,
workdir,
&vendor_boot_partition,
"vendor_ramdisk/init_boot.cpio",
)? {
println!("- Using vendor_boot partition (vendor_ramdisk/init_boot.cpio).");
selected_partition = &vendor_boot_partition;
}
}
// try vendor_boot/vendor_ramdisk/ramdisk.cpio
if selected_partition == &boot_partition
&& !is_replace_kernel
&& vendor_boot_exist
&& !skip_init
{
if unpack_and_check_init(
magiskboot,
workdir,
&vendor_boot_partition,
"vendor_ramdisk/ramdisk.cpio",
)? {
println!("- Using vendor_boot partition (vendor_ramdisk/ramdisk.cpio).");
selected_partition = &vendor_boot_partition;
}
}
if selected_partition == &boot_partition {
println!("- Using boot partition (ramdisk.cpio).");
}
println!("- Bootdevice: {selected_partition}");
println!("- Bootdevice: {boot_partition}");
let tmp_boot_path = workdir.join("boot.img");
dd(selected_partition, &tmp_boot_path)?;
dd(&boot_partition, &tmp_boot_path)?;
ensure!(tmp_boot_path.exists(), "- Tmp boot image not found");
ensure!(tmp_boot_path.exists(), "boot image not found");
bootimage = tmp_boot_path;
bootdevice = Some(selected_partition.to_string());
bootdevice = Some(boot_partition);
};
Ok((bootimage, bootdevice))
}
#[cfg(target_os = "android")]
pub fn choose_boot_partition(
kmi: &str,
is_replace_kernel: bool,
partition: &Option<String>,
) -> String {
let slot_suffix = get_slot_suffix(false);
let skip_init_boot = kmi.starts_with("android12-");
let init_boot_exist = Path::new(&format!("/dev/block/by-name/init_boot{slot_suffix}")).exists();
// if specific partition is specified, use it
if let Some(part) = partition {
return match part.as_str() {
"boot" | "init_boot" | "vendor_boot" => part.clone(),
_ => "boot".to_string(),
};
}
// if init_boot exists and not skipping it, use it
if !is_replace_kernel && init_boot_exist && !skip_init_boot {
return "init_boot".to_string();
}
"boot".to_string()
}
#[cfg(not(target_os = "android"))]
pub fn choose_boot_partition(
_kmi: &str,
_is_replace_kernel: bool,
_partition: &Option<String>,
) -> String {
"boot".to_string()
}
#[cfg(target_os = "android")]
pub fn get_slot_suffix(ota: bool) -> String {
let mut slot_suffix = utils::getprop("ro.boot.slot_suffix").unwrap_or_else(|| String::from(""));
if !slot_suffix.is_empty() && ota {
if slot_suffix == "_a" {
slot_suffix = "_b".to_string()
} else {
slot_suffix = "_a".to_string()
}
}
slot_suffix
}
#[cfg(not(target_os = "android"))]
pub fn get_slot_suffix(_ota: bool) -> String {
String::new()
}
#[cfg(target_os = "android")]
pub fn list_available_partitions() -> Vec<String> {
let slot_suffix = get_slot_suffix(false);
let candidates = vec!["boot", "init_boot", "vendor_boot"];
candidates
.into_iter()
.filter(|name| Path::new(&format!("/dev/block/by-name/{}{}", name, slot_suffix)).exists())
.map(|s| s.to_string())
.collect()
}
#[cfg(not(target_os = "android"))]
pub fn list_available_partitions() -> Vec<String> {
Vec::new()
}
fn post_ota() -> Result<()> {
use crate::defs::ADB_DIR;
use assets::BOOTCTL_PATH;
@ -1034,4 +788,4 @@ rm -f /data/adb/post-fs-data.d/post_ota.sh
std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?;
Ok(())
}
}

View file

@ -7,8 +7,9 @@ use android_logger::Config;
#[cfg(target_os = "android")]
use log::LevelFilter;
use crate::defs::KSUD_VERBOSE_LOG_FILE;
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
use crate::{
apk_sign, assets, debug, defs, defs::KSUD_VERBOSE_LOG_FILE, init_event, ksucalls, module, utils,
};
/// KernelSU userspace cli
#[derive(Parser, Debug)]
@ -63,6 +64,12 @@ enum Commands {
command: Profile,
},
/// Manage kernel features
Feature {
#[command(subcommand)]
command: Feature,
},
/// Patch boot or init_boot images to apply KernelSU
BootPatch {
/// boot image path, if not specified, will try to find the boot image automatically
@ -100,6 +107,10 @@ enum Commands {
/// KMI version, if specified, will use the specified KMI
#[arg(long, default_value = None)]
kmi: Option<String>,
/// target partition override (init_boot | boot | vendor_boot)
#[arg(long, default_value = None)]
partition: Option<String>,
},
/// Restore boot or init_boot images patched by KernelSU
@ -122,6 +133,20 @@ enum Commands {
#[command(subcommand)]
command: BootInfo,
},
/// KPM module manager
#[cfg(target_arch = "aarch64")]
Kpm {
#[command(subcommand)]
command: kpm_cmd::Kpm,
},
/// Manage kernel umount paths
Umount {
#[command(subcommand)]
command: Umount,
},
/// For developers
Debug {
#[command(subcommand)]
@ -135,7 +160,23 @@ enum BootInfo {
CurrentKmi,
/// show supported kmi versions
SupportedKmi,
SupportedKmis,
/// check if device is A/B capable
IsAbDevice,
/// show auto-selected boot partition name
DefaultPartition,
/// list available partitions for current or OTA toggled slot
AvailablePartitions,
/// show slot suffix for current or OTA toggled slot
SlotSuffix {
/// toggle to another slot
#[arg(short = 'u', long, default_value = "false")]
ota: bool,
},
}
#[derive(clap::Subcommand, Debug)]
@ -167,6 +208,39 @@ enum Debug {
/// For testing
Test,
/// Process mark management
Mark {
#[command(subcommand)]
command: MarkCommand,
},
}
#[derive(clap::Subcommand, Debug)]
enum MarkCommand {
/// Get mark status for a process (or all)
Get {
/// target pid (0 for total count)
#[arg(default_value = "0")]
pid: i32,
},
/// Mark a process
Mark {
/// target pid (0 for all processes)
#[arg(default_value = "0")]
pid: i32,
},
/// Unmark a process
Unmark {
/// target pid (0 for all processes)
#[arg(default_value = "0")]
pid: i32,
},
/// Refresh mark for all running processes
Refresh,
}
#[derive(clap::Subcommand, Debug)]
@ -272,6 +346,100 @@ enum Profile {
ListTemplates,
}
#[derive(clap::Subcommand, Debug)]
enum Feature {
/// Get feature value and support status
Get {
/// Feature ID or name (su_compat, kernel_umount)
id: String,
},
/// Set feature value
Set {
/// Feature ID or name
id: String,
/// Feature value (0=disable, 1=enable)
value: u64,
},
/// List all available features
List,
/// Check feature status (supported/unsupported/managed)
Check {
/// Feature ID or name (su_compat, kernel_umount)
id: String,
},
/// Load configuration from file and apply to kernel
Load,
/// Save current kernel feature states to file
Save,
}
#[cfg(target_arch = "aarch64")]
mod kpm_cmd {
use clap::Subcommand;
use std::path::PathBuf;
#[derive(Subcommand, Debug)]
pub enum Kpm {
/// Load a KPM module: load <path> [args]
Load { path: PathBuf, args: Option<String> },
/// Unload a KPM module: unload <name>
Unload { name: String },
/// Get number of loaded modules
Num,
/// List loaded KPM modules
List,
/// Get info of a KPM module: info <name>
Info { name: String },
/// Send control command to a KPM module: control <name> <args>
Control { name: String, args: String },
/// Print KPM Loader version
Version,
}
}
#[derive(clap::Subcommand, Debug)]
enum Umount {
/// Add custom umount path
Add {
/// Mount path to add
path: String,
/// Check mount type (overlay)
#[arg(long, default_value = "false")]
check_mnt: bool,
/// Umount flags (0 or 8 for MNT_DETACH)
#[arg(long, default_value = "-1")]
flags: i32,
},
/// Remove custom umount path
Remove {
/// Mount path to remove
path: String,
},
/// List all umount paths
List,
/// Clear all custom paths (keep defaults)
ClearCustom,
/// Save configuration to file
Save,
/// Load and apply configuration from file
Load,
/// Apply current configuration to kernel
Apply,
}
pub fn run() -> Result<()> {
#[cfg(target_os = "android")]
android_logger::init_once(
@ -335,6 +503,15 @@ pub fn run() -> Result<()> {
Profile::ListTemplates => crate::profile::list_templates(),
},
Commands::Feature { command } => match command {
Feature::Get { id } => crate::feature::get_feature(id),
Feature::Set { id, value } => crate::feature::set_feature(id, value),
Feature::List => crate::feature::list_features(),
Feature::Check { id } => crate::feature::check_feature(id),
Feature::Load => crate::feature::load_config_and_apply(),
Feature::Save => crate::feature::save_config(),
},
Commands::Debug { command } => match command {
Debug::SetManager { apk } => debug::set_manager(&apk),
Debug::GetSign { apk } => {
@ -349,6 +526,12 @@ pub fn run() -> Result<()> {
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
Debug::Mount => init_event::mount_modules_systemlessly(),
Debug::Test => assets::ensure_binaries(false),
Debug::Mark { command } => match command {
MarkCommand::Get { pid } => debug::mark_get(pid),
MarkCommand::Mark { pid } => debug::mark_set(pid),
MarkCommand::Unmark { pid } => debug::mark_unset(pid),
MarkCommand::Refresh => debug::mark_refresh(),
},
},
Commands::BootPatch {
@ -361,7 +544,10 @@ pub fn run() -> Result<()> {
out,
magiskboot,
kmi,
} => crate::boot_patch::patch(boot, kernel, module, init, ota, flash, out, magiskboot, kmi),
partition,
} => crate::boot_patch::patch(
boot, kernel, module, init, ota, flash, out, magiskboot, kmi, partition,
),
Commands::BootInfo { command } => match command {
BootInfo::CurrentKmi => {
@ -370,21 +556,80 @@ pub fn run() -> Result<()> {
// return here to avoid printing the error message
return Ok(());
}
BootInfo::SupportedKmi => {
BootInfo::SupportedKmis => {
let kmi = crate::assets::list_supported_kmi()?;
kmi.iter().for_each(|kmi| println!("{kmi}"));
return Ok(());
}
BootInfo::IsAbDevice => {
let val = crate::utils::getprop("ro.build.ab_update")
.unwrap_or_else(|| String::from("false"));
let is_ab = val.trim().to_lowercase() == "true";
println!("{}", if is_ab { "true" } else { "false" });
return Ok(());
}
BootInfo::DefaultPartition => {
let kmi = crate::boot_patch::get_current_kmi().unwrap_or_else(|_| String::from(""));
let name = crate::boot_patch::choose_boot_partition(&kmi, false, &None);
println!("{name}");
return Ok(());
}
BootInfo::SlotSuffix { ota } => {
let suffix = crate::boot_patch::get_slot_suffix(ota);
println!("{suffix}");
return Ok(());
}
BootInfo::AvailablePartitions => {
let parts = crate::boot_patch::list_available_partitions();
parts.iter().for_each(|p| println!("{p}"));
return Ok(());
}
},
Commands::BootRestore {
boot,
magiskboot,
flash,
} => crate::boot_patch::restore(boot, magiskboot, flash),
#[cfg(target_arch = "aarch64")]
Commands::Kpm { command } => {
use crate::cli::kpm_cmd::Kpm;
match command {
Kpm::Load { path, args } => {
crate::kpm::kpm_load(path.to_str().unwrap(), args.as_deref())
}
Kpm::Unload { name } => crate::kpm::kpm_unload(&name),
Kpm::Num => crate::kpm::kpm_num().map(|_| ()),
Kpm::List => crate::kpm::kpm_list(),
Kpm::Info { name } => crate::kpm::kpm_info(&name),
Kpm::Control { name, args } => {
let ret = crate::kpm::kpm_control(&name, &args)?;
println!("{}", ret);
Ok(())
}
Kpm::Version => crate::kpm::kpm_version_loader(),
}
}
Commands::Umount { command } => match command {
Umount::Add {
path,
check_mnt,
flags,
} => crate::umount_manager::add_umount_path(&path, check_mnt, flags),
Umount::Remove { path } => crate::umount_manager::remove_umount_path(&path),
Umount::List => crate::umount_manager::list_umount_paths(),
Umount::ClearCustom => crate::umount_manager::clear_custom_paths(),
Umount::Save => crate::umount_manager::save_umount_config(),
Umount::Load => crate::umount_manager::load_and_apply_config(),
Umount::Apply => crate::umount_manager::apply_config_to_kernel(),
},
};
if let Err(e) = &result {
log::error!("Error: {e:?}");
for c in e.chain() {
log::error!("{c:#?}");
}
log::error!("{:#?}", e.backtrace());
}
result
}

View file

@ -1,9 +1,11 @@
use anyhow::{Context, Ok, Result, ensure};
use anyhow::{Context, Ok, Result, bail, ensure};
use std::{
path::{Path, PathBuf},
process::Command,
};
use crate::ksucalls;
const KERNEL_PARAM_PATH: &str = "/sys/module/kernelsu";
fn read_u32(path: &PathBuf) -> Result<u32> {
@ -50,3 +52,47 @@ pub fn set_manager(pkg: &str) -> Result<()> {
let _ = Command::new("am").args(["force-stop", pkg]).status();
Ok(())
}
/// Get mark status for a process
pub fn mark_get(pid: i32) -> Result<()> {
let result = ksucalls::mark_get(pid)?;
if pid == 0 {
bail!("Please specify a pid to get its mark status");
} else {
println!(
"Process {} mark status: {}",
pid,
if result != 0 { "marked" } else { "unmarked" }
);
}
Ok(())
}
/// Mark a process
pub fn mark_set(pid: i32) -> Result<()> {
ksucalls::mark_set(pid)?;
if pid == 0 {
println!("All processes marked successfully");
} else {
println!("Process {} marked successfully", pid);
}
Ok(())
}
/// Unmark a process
pub fn mark_unset(pid: i32) -> Result<()> {
ksucalls::mark_unset(pid)?;
if pid == 0 {
println!("All processes unmarked successfully");
} else {
println!("Process {} unmarked successfully", pid);
}
Ok(())
}
/// Refresh mark for all running processes
pub fn mark_refresh() -> Result<()> {
ksucalls::mark_refresh()?;
println!("Refreshed mark for all running processes");
Ok(())
}

View file

@ -0,0 +1,404 @@
use anyhow::{Context, Result, bail};
use const_format::concatcp;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use crate::defs;
const FEATURE_CONFIG_PATH: &str = concatcp!(defs::WORKING_DIR, ".feature_config");
const FEATURE_MAGIC: u32 = 0x7f4b5355;
const FEATURE_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FeatureId {
SuCompat = 0,
KernelUmount = 1,
EnhancedSecurity = 2,
}
impl FeatureId {
pub fn from_u32(id: u32) -> Option<Self> {
match id {
0 => Some(FeatureId::SuCompat),
1 => Some(FeatureId::KernelUmount),
2 => Some(FeatureId::EnhancedSecurity),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
FeatureId::SuCompat => "su_compat",
FeatureId::KernelUmount => "kernel_umount",
FeatureId::EnhancedSecurity => "enhanced_security",
}
}
pub fn description(&self) -> &'static str {
match self {
FeatureId::SuCompat => {
"SU Compatibility Mode - allows authorized apps to gain root via traditional 'su' command"
}
FeatureId::KernelUmount => {
"Kernel Umount - controls whether kernel automatically unmounts modules when not needed"
}
FeatureId::EnhancedSecurity => {
"Enhanced Security - disable nonKSU root elevation and unauthorized UID downgrades"
}
}
}
}
fn parse_feature_id(name: &str) -> Result<FeatureId> {
match name {
"su_compat" | "0" => Ok(FeatureId::SuCompat),
"kernel_umount" | "1" => Ok(FeatureId::KernelUmount),
"enhanced_security" | "2" => Ok(FeatureId::EnhancedSecurity),
_ => bail!("Unknown feature: {}", name),
}
}
pub fn load_binary_config() -> Result<HashMap<u32, u64>> {
let path = Path::new(FEATURE_CONFIG_PATH);
if !path.exists() {
log::info!("Feature config not found, using defaults");
return Ok(HashMap::new());
}
let mut file = File::open(path).with_context(|| "Failed to open feature config")?;
let mut magic_buf = [0u8; 4];
file.read_exact(&mut magic_buf)
.with_context(|| "Failed to read magic")?;
let magic = u32::from_le_bytes(magic_buf);
if magic != FEATURE_MAGIC {
bail!(
"Invalid feature config magic: expected 0x{:08x}, got 0x{:08x}",
FEATURE_MAGIC,
magic
);
}
let mut version_buf = [0u8; 4];
file.read_exact(&mut version_buf)
.with_context(|| "Failed to read version")?;
let version = u32::from_le_bytes(version_buf);
if version != FEATURE_VERSION {
log::warn!(
"Feature config version mismatch: expected {}, got {}",
FEATURE_VERSION,
version
);
}
let mut count_buf = [0u8; 4];
file.read_exact(&mut count_buf)
.with_context(|| "Failed to read count")?;
let count = u32::from_le_bytes(count_buf);
let mut features = HashMap::new();
for _ in 0..count {
let mut id_buf = [0u8; 4];
let mut value_buf = [0u8; 8];
file.read_exact(&mut id_buf)
.with_context(|| "Failed to read feature id")?;
file.read_exact(&mut value_buf)
.with_context(|| "Failed to read feature value")?;
let id = u32::from_le_bytes(id_buf);
let value = u64::from_le_bytes(value_buf);
features.insert(id, value);
}
log::info!("Loaded {} features from config", features.len());
Ok(features)
}
pub fn save_binary_config(features: &HashMap<u32, u64>) -> Result<()> {
crate::utils::ensure_dir_exists(Path::new(defs::WORKING_DIR))?;
let path = Path::new(FEATURE_CONFIG_PATH);
let mut file = File::create(path).with_context(|| "Failed to create feature config")?;
file.write_all(&FEATURE_MAGIC.to_le_bytes())
.with_context(|| "Failed to write magic")?;
file.write_all(&FEATURE_VERSION.to_le_bytes())
.with_context(|| "Failed to write version")?;
let count = features.len() as u32;
file.write_all(&count.to_le_bytes())
.with_context(|| "Failed to write count")?;
for (&id, &value) in features.iter() {
file.write_all(&id.to_le_bytes())
.with_context(|| format!("Failed to write feature id {}", id))?;
file.write_all(&value.to_le_bytes())
.with_context(|| format!("Failed to write feature value for id {}", id))?;
}
file.sync_all()
.with_context(|| "Failed to sync feature config")?;
log::info!("Saved {} features to config", features.len());
Ok(())
}
pub fn apply_config(features: &HashMap<u32, u64>) -> Result<()> {
log::info!("Applying feature configuration to kernel...");
let mut applied = 0;
for (&id, &value) in features.iter() {
match crate::ksucalls::set_feature(id, value) {
Ok(_) => {
if let Some(feature_id) = FeatureId::from_u32(id) {
log::info!("Set feature {} to {}", feature_id.name(), value);
} else {
log::info!("Set feature {} to {}", id, value);
}
applied += 1;
}
Err(e) => {
log::warn!("Failed to set feature {}: {}", id, e);
}
}
}
log::info!("Applied {} features successfully", applied);
Ok(())
}
pub fn get_feature(id: String) -> Result<()> {
let feature_id = parse_feature_id(&id)?;
let (value, supported) = crate::ksucalls::get_feature(feature_id as u32)
.with_context(|| format!("Failed to get feature {}", id))?;
if !supported {
println!("Feature '{}' is not supported by kernel", id);
return Ok(());
}
println!("Feature: {} ({})", feature_id.name(), feature_id as u32);
println!("Description: {}", feature_id.description());
println!("Value: {}", value);
println!(
"Status: {}",
if value != 0 { "enabled" } else { "disabled" }
);
Ok(())
}
pub fn set_feature(id: String, value: u64) -> Result<()> {
let feature_id = parse_feature_id(&id)?;
crate::ksucalls::set_feature(feature_id as u32, value)
.with_context(|| format!("Failed to set feature {} to {}", id, value))?;
println!(
"Feature '{}' set to {} ({})",
feature_id.name(),
value,
if value != 0 { "enabled" } else { "disabled" }
);
Ok(())
}
pub fn list_features() -> Result<()> {
println!("Available Features:");
println!("{}", "=".repeat(80));
// Get managed features from modules
let managed_features_map = crate::module::get_managed_features().unwrap_or_default();
// Build a reverse map: feature_name -> Vec<module_id>
let mut feature_to_modules: HashMap<String, Vec<String>> = HashMap::new();
for (module_id, feature_list) in managed_features_map.iter() {
for feature_name in feature_list {
feature_to_modules
.entry(feature_name.clone())
.or_default()
.push(module_id.clone());
}
}
let all_features = [
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
];
for feature_id in all_features.iter() {
let id = *feature_id as u32;
let (value, supported) = crate::ksucalls::get_feature(id).unwrap_or((0, false));
let status = if !supported {
"NOT_SUPPORTED".to_string()
} else if value != 0 {
format!("ENABLED ({})", value)
} else {
"DISABLED".to_string()
};
let managed_by = feature_to_modules.get(feature_id.name());
let managed_mark = if managed_by.is_some() {
" [MODULE_MANAGED]"
} else {
""
};
println!(
"[{}] {} (ID={}){}",
status,
feature_id.name(),
id,
managed_mark
);
println!(" {}", feature_id.description());
if let Some(modules) = managed_by {
println!(
" ⚠️ Managed by module(s): {} (forced to 0 on initialization)",
modules.join(", ")
);
}
println!();
}
Ok(())
}
pub fn load_config_and_apply() -> Result<()> {
let features = load_binary_config()?;
if features.is_empty() {
println!("No features found in config file");
return Ok(());
}
apply_config(&features)?;
println!("Feature configuration loaded and applied");
Ok(())
}
pub fn save_config() -> Result<()> {
let mut features = HashMap::new();
let all_features = [
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
];
for feature_id in all_features.iter() {
let id = *feature_id as u32;
if let Ok((value, supported)) = crate::ksucalls::get_feature(id)
&& supported
{
features.insert(id, value);
log::info!("Saved feature {} = {}", feature_id.name(), value);
}
}
save_binary_config(&features)?;
println!(
"Current feature states saved to config file ({} features)",
features.len()
);
Ok(())
}
pub fn check_feature(id: String) -> Result<()> {
let feature_id = parse_feature_id(&id)?;
// Check if this feature is managed by any module
let managed_features_map = crate::module::get_managed_features().unwrap_or_default();
let is_managed = managed_features_map
.values()
.any(|features| features.iter().any(|f| f == feature_id.name()));
if is_managed {
println!("managed");
return Ok(());
}
// Check if the feature is supported by kernel
let (_value, supported) = crate::ksucalls::get_feature(feature_id as u32)
.with_context(|| format!("Failed to get feature {}", id))?;
if supported {
println!("supported");
} else {
println!("unsupported");
}
Ok(())
}
pub fn init_features() -> Result<()> {
log::info!("Initializing features from config...");
let mut features = load_binary_config()?;
// Get managed features from active modules
if let Ok(managed_features_map) = crate::module::get_managed_features() {
if !managed_features_map.is_empty() {
log::info!(
"Found {} modules managing features",
managed_features_map.len()
);
// Force override managed features to 0
for (module_id, feature_list) in managed_features_map.iter() {
log::info!(
"Module '{}' manages {} feature(s)",
module_id,
feature_list.len()
);
for feature_name in feature_list {
if let Ok(feature_id) = parse_feature_id(feature_name) {
let feature_id_u32 = feature_id as u32;
log::info!(
" - Force overriding managed feature '{}' to 0 (by module: {})",
feature_name,
module_id
);
features.insert(feature_id_u32, 0);
} else {
log::warn!(
" - Unknown managed feature '{}' from module '{}', ignoring",
feature_name,
module_id
);
}
}
}
}
} else {
log::warn!(
"Failed to get managed features from modules, continuing with normal initialization"
);
}
if features.is_empty() {
log::info!("No features to apply, skipping initialization");
return Ok(());
}
apply_config(&features)?;
// Save the final configuration (including managed features forced to 0)
save_binary_config(&features)?;
log::info!("Saved final feature configuration to file");
Ok(())
}

View file

@ -1,11 +1,29 @@
use crate::defs::{KSU_MOUNT_SOURCE, NO_MOUNT_PATH, NO_TMPFS_PATH};
use crate::module::{handle_updated_modules, prune_modules};
use crate::{assets, defs, ksucalls, restorecon, utils, kpm, uid_scanner};
#[cfg(target_arch = "aarch64")]
use crate::kpm;
use crate::utils::is_safe_mode;
use crate::{
assets, defs,
defs::{KSU_MOUNT_SOURCE, NO_MOUNT_PATH, NO_TMPFS_PATH},
ksucalls,
module::{handle_updated_modules, prune_modules},
restorecon, uid_scanner, utils,
utils::find_tmp_path,
};
use anyhow::{Context, Result};
use log::{info, warn};
use rustix::fs::{MountFlags, mount};
use std::path::Path;
#[cfg(target_os = "android")]
pub fn mount_modules_systemlessly() -> Result<()> {
crate::magic_mount::magic_mount(&find_tmp_path())
}
#[cfg(not(target_os = "android"))]
pub fn mount_modules_systemlessly() -> Result<()> {
Ok(())
}
pub fn on_post_data_fs() -> Result<()> {
ksucalls::report_post_fs_data();
@ -37,6 +55,11 @@ pub fn on_post_data_fs() -> Result<()> {
// Start UID scanner daemon with highest priority
uid_scanner::start_uid_scanner_daemon()?;
if is_safe_mode() {
warn!("safe mode, skip load feature config");
} else if let Err(e) = crate::umount_manager::load_and_apply_config() {
warn!("Failed to load umount config: {e}");
}
// tell kernel that we've mount the module, so that it can do some optimization
ksucalls::report_module_mounted();
@ -50,11 +73,11 @@ pub fn on_post_data_fs() -> Result<()> {
}
if let Err(e) = prune_modules() {
warn!("prune modules failed: {}", e);
warn!("prune modules failed: {e}");
}
if let Err(e) = handle_updated_modules() {
warn!("handle updated modules failed: {}", e);
warn!("handle updated modules failed: {e}");
}
if let Err(e) = restorecon::restorecon() {
@ -70,24 +93,37 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("apply root profile sepolicy failed: {e}");
}
if let Err(e) = kpm::start_kpm_watcher() {
warn!("KPM: Failed to start KPM watcher: {}", e);
// load feature config
if is_safe_mode() {
warn!("safe mode, skip load feature config");
} else if let Err(e) = crate::feature::init_features() {
warn!("init features failed: {e}");
}
if let Err(e) = kpm::load_kpm_modules() {
warn!("KPM: Failed to load KPM modules: {}", e);
#[cfg(target_arch = "aarch64")]
if let Err(e) = kpm::start_kpm_watcher() {
warn!("KPM: Failed to start KPM watcher: {e}");
}
#[cfg(target_arch = "aarch64")]
if let Err(e) = kpm::load_kpm_modules() {
warn!("KPM: Failed to load KPM modules: {e}");
}
let tmpfs_path = find_tmp_path();
// for compatibility
let no_mount = Path::new(NO_TMPFS_PATH).exists() || Path::new(NO_MOUNT_PATH).exists();
// mount temp dir
if !Path::new(NO_TMPFS_PATH).exists() {
if !no_mount {
if let Err(e) = mount(
KSU_MOUNT_SOURCE,
utils::get_tmp_path(),
&tmpfs_path,
"tmpfs",
MountFlags::empty(),
"",
) {
warn!("do temp dir mount failed: {}", e);
warn!("do temp dir mount failed: {e}");
}
} else {
info!("no tmpfs requested");
@ -105,9 +141,10 @@ pub fn on_post_data_fs() -> Result<()> {
}
// mount module systemlessly by magic mount
if !Path::new(NO_MOUNT_PATH).exists() {
if let Err(e) = mount_modules_systemlessly() {
warn!("do systemless mount failed: {}", e);
#[cfg(target_os = "android")]
if !no_mount {
if let Err(e) = crate::magic_mount::magic_mount(&tmpfs_path) {
warn!("do systemless mount failed: {e}");
}
} else {
info!("no mount requested");
@ -118,16 +155,6 @@ pub fn on_post_data_fs() -> Result<()> {
Ok(())
}
#[cfg(target_os = "android")]
pub fn mount_modules_systemlessly() -> Result<()> {
crate::magic_mount::magic_mount()
}
#[cfg(not(target_os = "android"))]
pub fn mount_modules_systemlessly() -> Result<()> {
Ok(())
}
fn run_stage(stage: &str, block: bool) {
utils::umask(0);

View file

@ -248,6 +248,42 @@ api_level_arch_detect() {
# Module Related
#################
check_managed_features() {
local PROP_FILE=$1
local MANAGED_FEATURES=$(grep_prop managedFeatures "$PROP_FILE")
[ -z "$MANAGED_FEATURES" ] && return 0
ui_print "- Checking managed features: $MANAGED_FEATURES"
# Split features by comma
echo "$MANAGED_FEATURES" | tr ',' '\n' | while read -r feature; do
# Trim whitespace
feature=$(echo "$feature" | xargs)
[ -z "$feature" ] && continue
# Check feature status using ksud
local status=$(/data/adb/ksud feature check "$feature" 2>/dev/null)
case "$status" in
"unsupported")
ui_print "! WARNING: Feature '$feature' is NOT SUPPORTED by kernel"
ui_print "! This module may not work correctly!"
;;
"managed")
ui_print "! WARNING: Feature '$feature' is already MANAGED by another module"
ui_print "! Feature conflicts may occur!"
;;
"supported")
ui_print "- Feature '$feature' is supported and available"
;;
*)
ui_print "! WARNING: Unable to check feature '$feature' status"
;;
esac
done
}
set_perm() {
chown $2:$3 $1 || return 1
chmod $4 $1 || return 1
@ -344,6 +380,9 @@ install_module() {
MODAUTH=`grep_prop author $TMPDIR/module.prop`
MODPATH=$MODULEROOT/$MODID
# Check managed features
check_managed_features $TMPDIR/module.prop
# Create mod paths
rm -rf $MODPATH
mkdir -p $MODPATH

View file

@ -1,296 +1,331 @@
use anyhow::{anyhow, Result};
use libc::{prctl, c_char, c_void, c_int};
use std::{
ffi::{CStr, CString, OsStr},
fs,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
use anyhow::{Result, bail};
use notify::{RecursiveMode, Watcher};
use std::ffi::{CStr, CString, OsStr};
use std::fs;
use std::path::Path;
use std::ptr;
use std::os::unix::fs::PermissionsExt;
use crate::ksucalls::ksuctl;
pub const KPM_DIR: &str = "/data/adb/kpm";
const KSU_OPTIONS: u32 = 0xdeadbeef;
const SUKISU_KPM_LOAD: i32 = 28;
const SUKISU_KPM_UNLOAD: i32 = 29;
const SUKISU_KPM_VERSION: i32 = 34;
const KPM_LOAD: u64 = 1;
const KPM_UNLOAD: u64 = 2;
const KPM_NUM: u64 = 3;
const KPM_LIST: u64 = 4;
const KPM_INFO: u64 = 5;
const KPM_CONTROL: u64 = 6;
const KPM_VERSION: u64 = 7;
pub fn check_kpm_version() -> Result<String> {
let mut buffer: [u8; 1024] = [0; 1024];
let mut out: c_int = -1;
let _ret = unsafe {
prctl(
KSU_OPTIONS as c_int,
SUKISU_KPM_VERSION,
buffer.as_mut_ptr() as *mut c_void,
buffer.len() as *mut c_void,
&mut out as *mut c_int as *mut c_void,
)
};
const KSU_IOCTL_KPM: u32 = 0xc0004bc8; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 200, 0)
if out < 0 {
return Err(anyhow!("KPM: prctl returned error: {}", out));
}
let version_str = unsafe {
CStr::from_ptr(buffer.as_ptr() as *const c_char)
}.to_string_lossy().to_string();
log::info!("KPM: Version check result: {}", version_str);
// 检查版本是否有效不为空且不以Error开头
if version_str.is_empty() || version_str.starts_with("Error") {
return Err(anyhow!("KPM: Invalid version response: {}", version_str));
}
Ok(version_str)
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct KsuKpmCmd {
pub control_code: u64,
pub arg1: u64,
pub arg2: u64,
pub result_code: u64,
}
// 确保 KPM 目录存在,并设置777权限
fn kpm_ioctl(cmd: &mut KsuKpmCmd) -> std::io::Result<()> {
ksuctl(KSU_IOCTL_KPM, cmd as *mut _)?;
Ok(())
}
/// Convert raw kernel return code to `Result`.
fn check_ret(rc: i32) -> Result<i32> {
if rc < 0 {
bail!("KPM error: {}", std::io::Error::from_raw_os_error(-rc));
}
Ok(rc)
}
/// Load a `.kpm` into kernel space.
pub fn kpm_load<P>(path: P, args: Option<&str>) -> Result<()>
where
P: AsRef<Path>,
{
let path_c = CString::new(path.as_ref().to_string_lossy().to_string())?;
let args_c = args.map(CString::new).transpose()?;
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_LOAD,
arg1: path_c.as_ptr() as u64,
arg2: args_c.as_ref().map_or(0, |s| s.as_ptr() as u64),
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
println!("Success");
Ok(())
}
/// Unload by module name.
pub fn kpm_unload(name: &str) -> Result<()> {
let name_c = CString::new(name)?;
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_UNLOAD,
arg1: name_c.as_ptr() as u64,
arg2: 0,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
Ok(())
}
/// Return loaded module count.
pub fn kpm_num() -> Result<i32> {
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_NUM,
arg1: 0,
arg2: 0,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
let n = check_ret(result)?;
println!("{n}");
Ok(n)
}
/// Print name list of loaded modules.
pub fn kpm_list() -> Result<()> {
let mut buf = vec![0u8; 1024];
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_LIST,
arg1: buf.as_mut_ptr() as u64,
arg2: buf.len() as u64,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
print!("{}", buf2str(&buf));
Ok(())
}
/// Print single module info.
pub fn kpm_info(name: &str) -> Result<()> {
let name_c = CString::new(name)?;
let mut buf = vec![0u8; 256];
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_INFO,
arg1: name_c.as_ptr() as u64,
arg2: buf.as_mut_ptr() as u64,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
println!("{}", buf2str(&buf));
Ok(())
}
/// Send control string to a module; returns kernel answer.
pub fn kpm_control(name: &str, args: &str) -> Result<i32> {
let name_c = CString::new(name)?;
let args_c = CString::new(args)?;
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_CONTROL,
arg1: name_c.as_ptr() as u64,
arg2: args_c.as_ptr() as u64,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)
}
/// Print loader version string.
pub fn kpm_version_loader() -> Result<()> {
let mut buf = vec![0u8; 1024];
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_VERSION,
arg1: buf.as_mut_ptr() as u64,
arg2: buf.len() as u64,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
print!("{}", buf2str(&buf));
Ok(())
}
/// Validate loader version; empty or "Error*" => fail.
pub fn check_kpm_version() -> Result<String> {
let mut buf = vec![0u8; 1024];
let mut result: i32 = -1;
let mut cmd = KsuKpmCmd {
control_code: KPM_VERSION,
arg1: buf.as_mut_ptr() as u64,
arg2: buf.len() as u64,
result_code: &mut result as *mut i32 as u64,
};
kpm_ioctl(&mut cmd)?;
check_ret(result)?;
let ver = buf2str(&buf);
if ver.is_empty() {
bail!("KPM: invalid version response: {ver}");
}
log::info!("KPM: version check ok: {ver}");
Ok(ver)
}
/// Create `/data/adb/kpm` with 0o777 if missing.
pub fn ensure_kpm_dir() -> Result<()> {
let path = Path::new(KPM_DIR);
if path.exists() {
let meta = fs::metadata(path)?;
let current = meta.permissions().mode() & 0o777;
if current != 0o777 {
log::info!("KPM: Fixing permissions to 777 for {}", KPM_DIR);
fs::set_permissions(path, fs::Permissions::from_mode(0o777))?;
}
let _ = fs::create_dir_all(KPM_DIR);
let meta = fs::metadata(KPM_DIR)?;
if meta.permissions().mode() != 0o777 {
fs::set_permissions(KPM_DIR, fs::Permissions::from_mode(0o777))?;
}
Ok(())
}
/// Start file watcher for hot-(un)load.
pub fn start_kpm_watcher() -> Result<()> {
match check_kpm_version() {
Ok(version) => {
log::info!("KPM: Version check passed, version: {}", version);
}
Err(e) => {
log::warn!("KPM: Version check failed, skipping KPM functionality: {}", e);
return Ok(())
}
}
check_kpm_version()?; // bails if loader too old
ensure_kpm_dir()?;
// 检查是否处于安全模式
if crate::utils::is_safe_mode() {
log::warn!("KPM: System is in safe mode, removing all KPM modules");
if let Err(e) = remove_all_kpms() {
log::error!("KPM: Error removing all KPM modules: {}", e);
}
log::warn!("KPM: safe-mode removing all modules");
remove_all_kpms()?;
return Ok(());
}
let mut watcher = notify::recommended_watcher(|res| match res {
Ok(event) => handle_kpm_event(event),
Err(e) => log::error!("KPM: File monitoring error: {:?}", e),
let mut watcher = notify::recommended_watcher(|res: Result<_, _>| match res {
Ok(evt) => handle_kpm_event(evt),
Err(e) => log::error!("KPM: watcher error: {e}"),
})?;
watcher.watch(Path::new(KPM_DIR), RecursiveMode::NonRecursive)?;
log::info!("KPM: Started file watcher for directory: {}", KPM_DIR);
log::info!("KPM: watcher active on {KPM_DIR}");
Ok(())
}
// 处理 KPM 事件
pub fn handle_kpm_event(event: notify::Event) {
match event.kind {
notify::EventKind::Create(_) => handle_create_event(event.paths),
notify::EventKind::Remove(_) => handle_remove_event(event.paths),
notify::EventKind::Modify(_) => handle_modify_event(event.paths),
_ => {}
}
}
fn handle_create_event(paths: Vec<std::path::PathBuf>) {
for path in paths {
if path.extension() == Some(OsStr::new("kpm")) {
log::info!("KPM: Detected new KPM file: {}", path.display());
if let Err(e) = load_kpm(&path) {
log::warn!("KPM: Failed to load {}: {}", path.display(), e);
fn handle_kpm_event(evt: notify::Event) {
if let notify::EventKind::Create(_) = evt.kind {
for p in evt.paths {
if let Some(ex) = p.extension()
&& ex == OsStr::new("kpm")
&& kpm_load(&p, None).is_err()
{
log::warn!("KPM: failed to load {}", p.display());
}
}
}
}
fn handle_remove_event(paths: Vec<std::path::PathBuf>) {
for path in paths {
if let Some(name) = path.file_stem().and_then(|s| s.to_str()) {
log::info!("KPM: Detected KPM file removal: {}", name);
if let Err(e) = unload_kpm(name) {
log::warn!("KPM: Failed to unload {}: {}", name, e);
}
}
}
}
/// Locate `/data/adb/kpm/<name>.kpm`.
fn find_kpm_file(name: &str) -> Result<Option<PathBuf>> {
let dir = Path::new(KPM_DIR);
fn handle_modify_event(paths: Vec<std::path::PathBuf>) {
for path in paths {
log::info!("KPM: Modified file detected: {}", path.display());
}
}
// 加载 KPM 模块
pub fn load_kpm(path: &Path) -> Result<()> {
let path_str = path
.to_str()
.ok_or_else(|| anyhow!("KPM: Invalid path: {}", path.display()))?;
let path_cstring = CString::new(path_str)
.map_err(|e| anyhow!("KPM: Failed to convert path to CString: {}", e))?;
let mut out: c_int = -1;
let _ret = unsafe {
prctl(
KSU_OPTIONS as c_int,
SUKISU_KPM_LOAD,
path_cstring.as_ptr() as *mut c_void,
ptr::null_mut::<c_void>(),
&mut out as *mut c_int as *mut c_void,
)
};
if out < 0 {
return Err(anyhow!("KPM: prctl returned error: {}", out));
}
if out > 0 {
log::info!("KPM: Successfully loaded module: {}", path.display());
}
Ok(())
}
// 卸载 KPM 模块
pub fn unload_kpm(name: &str) -> Result<()> {
let name_cstring = CString::new(name)
.map_err(|e| anyhow!("KPM: Failed to convert name to CString: {}", e))?;
let mut out: c_int = -1;
let _ret = unsafe {
prctl(
KSU_OPTIONS as c_int,
SUKISU_KPM_UNLOAD,
name_cstring.as_ptr() as *mut c_void,
ptr::null_mut::<c_void>(),
&mut out as *mut c_int as *mut c_void,
)
};
if out < 0 {
log::warn!("KPM: prctl returned error for unload: {}", out);
return Err(anyhow!("KPM: prctl returned error: {}", out));
}
// 尝试删除对应的KPM文件
if let Ok(Some(path)) = find_kpm_file(name) {
if let Err(e) = fs::remove_file(&path) {
log::warn!("KPM: Failed to delete KPM file {}: {}", path.display(), e);
} else {
log::info!("KPM: Deleted KPM file: {}", path.display());
}
}
log::info!("KPM: Successfully unloaded module: {}", name);
Ok(())
}
// 通过名称查找 KPM 文件
fn find_kpm_file(name: &str) -> Result<Option<std::path::PathBuf>> {
let kpm_dir = Path::new(KPM_DIR);
if !kpm_dir.exists() {
if !dir.is_dir() {
return Ok(None);
}
for entry in fs::read_dir(kpm_dir)? {
let path = entry?.path();
if let Some(file_name) = path.file_stem() {
if let Some(file_name_str) = file_name.to_str() {
if file_name_str == name && path.extension() == Some(OsStr::new("kpm")) {
return Ok(Some(path));
}
}
for entry in fs::read_dir(dir)? {
let p = entry?.path();
if let Some(ex) = p.extension()
&& ex == OsStr::new("kpm")
&& let Some(fs) = p.file_stem()
&& fs == OsStr::new(name)
{
return Ok(Some(p));
}
}
Ok(None)
}
// 安全模式下删除所有 KPM 模块
/// Remove every `.kpm` file and unload it.
pub fn remove_all_kpms() -> Result<()> {
let kpm_dir = Path::new(KPM_DIR);
if !kpm_dir.exists() {
log::info!("KPM: KPM directory does not exist, nothing to remove");
let dir = Path::new(KPM_DIR);
if !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(KPM_DIR)? {
let path = entry?.path();
if path.extension().is_some_and(|ext| ext == "kpm") {
if let Some(name) = path.file_stem() {
let name_str = name.to_string_lossy();
log::info!("KPM: Removing module in safe mode: {}", name_str);
if let Err(e) = unload_kpm(&name_str) {
log::error!("KPM: Failed to remove module {}: {}", name_str, e);
for entry in fs::read_dir(dir)? {
let p = entry?.path();
if let Some(ex) = p.extension()
&& ex == OsStr::new("kpm")
&& let Some(name) = p.file_stem().and_then(|s| s.to_str())
&& let Err(e) = (|| -> Result<()> {
kpm_unload(name)?;
if let Some(p) = find_kpm_file(name)? {
if let Err(e) = fs::remove_file(&p) {
log::warn!("KPM: delete {} failed: {e}", p.display());
return Err(e.into());
}
log::info!("KPM: deleted {}", p.display());
}
if let Err(e) = fs::remove_file(&path) {
log::error!("KPM: Failed to delete file {}: {}", path.display(), e);
}
}
Ok(())
})()
{
log::error!("KPM: unload {name} failed: {e}");
}
}
Ok(())
}
// 加载所有 KPM 模块
/// Bulk-load existing `.kpm`s at boot.
pub fn load_kpm_modules() -> Result<()> {
match check_kpm_version() {
Ok(version) => {
log::info!("KPM: Version check passed before loading modules, version: {}", version);
}
Err(e) => {
log::warn!("KPM: Version check failed, skipping module loading: {}", e);
return Ok(());
}
}
check_kpm_version()?;
ensure_kpm_dir()?;
let kpm_dir = Path::new(KPM_DIR);
if !kpm_dir.exists() {
log::info!("KPM: KPM directory does not exist, no modules to load");
let dir = Path::new(KPM_DIR);
if !dir.is_dir() {
return Ok(());
}
let mut loaded_count = 0;
let mut failed_count = 0;
let (mut ok, mut ng) = (0, 0);
for entry in std::fs::read_dir(KPM_DIR)? {
let path = entry?.path();
if let Some(file_name) = path.file_stem() {
if let Some(file_name_str) = file_name.to_str() {
if file_name_str.is_empty() {
log::warn!("KPM: Invalid KPM file name: {}", path.display());
continue;
}
}
}
if path.extension().is_some_and(|ext| ext == "kpm") {
match load_kpm(&path) {
Ok(()) => {
log::info!("KPM: Successfully loaded module: {}", path.display());
loaded_count += 1;
}
for entry in fs::read_dir(dir)? {
let p = entry?.path();
if let Some(ex) = p.extension()
&& ex == OsStr::new("kpm")
{
match kpm_load(&p, None) {
Ok(_) => ok += 1,
Err(e) => {
log::warn!("KPM: Failed to load module {}: {}", path.display(), e);
failed_count += 1;
log::warn!("KPM: load {} failed: {e}", p.display());
ng += 1;
}
}
}
}
log::info!("KPM: Module loading completed - loaded: {}, failed: {}", loaded_count, failed_count);
log::info!("KPM: bulk-load done ok: {ok}, failed: {ng}");
Ok(())
}
/// Convert zero-padded kernel buffer to owned String.
fn buf2str(buf: &[u8]) -> String {
// SAFETY: buffer is always NUL-terminated by kernel.
unsafe {
CStr::from_ptr(buf.as_ptr().cast())
.to_string_lossy()
.into_owned()
}
}

View file

@ -1,10 +1,173 @@
const EVENT_POST_FS_DATA: u64 = 1;
const EVENT_BOOT_COMPLETED: u64 = 2;
const EVENT_MODULE_MOUNTED: u64 = 3;
use std::fs;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::fd::RawFd;
use std::sync::OnceLock;
// Event constants
const EVENT_POST_FS_DATA: u32 = 1;
const EVENT_BOOT_COMPLETED: u32 = 2;
const EVENT_MODULE_MOUNTED: u32 = 3;
const KSU_IOCTL_GRANT_ROOT: u32 = 0x00004b01; // _IOC(_IOC_NONE, 'K', 1, 0)
const KSU_IOCTL_GET_INFO: u32 = 0x80004b02; // _IOC(_IOC_READ, 'K', 2, 0)
const KSU_IOCTL_REPORT_EVENT: u32 = 0x40004b03; // _IOC(_IOC_WRITE, 'K', 3, 0)
const KSU_IOCTL_SET_SEPOLICY: u32 = 0xc0004b04; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 4, 0)
const KSU_IOCTL_CHECK_SAFEMODE: u32 = 0x80004b05; // _IOC(_IOC_READ, 'K', 5, 0)
const KSU_IOCTL_GET_FEATURE: u32 = 0xc0004b0d; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 13, 0)
const KSU_IOCTL_SET_FEATURE: u32 = 0x40004b0e; // _IOC(_IOC_WRITE, 'K', 14, 0)
const KSU_IOCTL_GET_WRAPPER_FD: u32 = 0x40004b0f; // _IOC(_IOC_WRITE, 'K', 15, 0)
const KSU_IOCTL_MANAGE_MARK: u32 = 0xc0004b10; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct GetInfoCmd {
version: u32,
flags: u32,
}
#[repr(C)]
struct ReportEventCmd {
event: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct SetSepolicyCmd {
pub cmd: u64,
pub arg: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct CheckSafemodeCmd {
in_safe_mode: u8,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct GetFeatureCmd {
feature_id: u32,
value: u64,
supported: u8,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct SetFeatureCmd {
feature_id: u32,
value: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct GetWrapperFdCmd {
fd: i32,
flags: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct ManageMarkCmd {
operation: u32,
pid: i32,
result: u32,
}
// Mark operation constants
const KSU_MARK_GET: u32 = 1;
const KSU_MARK_MARK: u32 = 2;
const KSU_MARK_UNMARK: u32 = 3;
const KSU_MARK_REFRESH: u32 = 4;
// Global driver fd cache
#[cfg(any(target_os = "linux", target_os = "android"))]
static DRIVER_FD: OnceLock<RawFd> = OnceLock::new();
#[cfg(any(target_os = "linux", target_os = "android"))]
static INFO_CACHE: OnceLock<GetInfoCmd> = OnceLock::new();
const KSU_INSTALL_MAGIC1: u32 = 0xDEADBEEF;
const KSU_INSTALL_MAGIC2: u32 = 0xCAFEBABE;
#[cfg(any(target_os = "linux", target_os = "android"))]
fn scan_driver_fd() -> Option<RawFd> {
let fd_dir = fs::read_dir("/proc/self/fd").ok()?;
for entry in fd_dir.flatten() {
if let Ok(fd_num) = entry.file_name().to_string_lossy().parse::<i32>() {
let link_path = format!("/proc/self/fd/{}", fd_num);
if let Ok(target) = fs::read_link(&link_path) {
let target_str = target.to_string_lossy();
if target_str.contains("[ksu_driver]") {
return Some(fd_num);
}
}
}
}
None
}
// Get cached driver fd
#[cfg(any(target_os = "linux", target_os = "android"))]
fn init_driver_fd() -> Option<RawFd> {
let fd = scan_driver_fd();
if fd.is_none() {
let mut fd = -1;
unsafe {
libc::syscall(
libc::SYS_reboot,
KSU_INSTALL_MAGIC1,
KSU_INSTALL_MAGIC2,
0,
&mut fd,
);
};
if fd >= 0 { Some(fd) } else { None }
} else {
fd
}
}
// ioctl wrapper using libc
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn ksuctl<T>(request: u32, arg: *mut T) -> std::io::Result<i32> {
use std::io;
let fd = *DRIVER_FD.get_or_init(|| init_driver_fd().unwrap_or(-1));
unsafe {
#[cfg(not(target_env = "gnu"))]
let ret = libc::ioctl(fd as libc::c_int, request as i32, arg);
#[cfg(target_env = "gnu")]
let ret = libc::ioctl(fd as libc::c_int, request as u64, arg);
if ret < 0 {
Err(io::Error::last_os_error())
} else {
Ok(ret)
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn ksuctl<T>(_request: u32, _arg: *mut T) -> std::io::Result<i32> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
// API implementations
#[cfg(any(target_os = "linux", target_os = "android"))]
fn get_info() -> GetInfoCmd {
*INFO_CACHE.get_or_init(|| {
let mut cmd = GetInfoCmd {
version: 0,
flags: 0,
};
let _ = ksuctl(KSU_IOCTL_GET_INFO, &mut cmd as *mut _);
cmd
})
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_version() -> i32 {
rustix::process::ksu_get_version()
get_info().version as i32
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
@ -13,22 +176,24 @@ pub fn get_version() -> i32 {
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn report_event(event: u64) {
rustix::process::ksu_report_event(event)
pub fn grant_root() -> std::io::Result<()> {
ksuctl(KSU_IOCTL_GRANT_ROOT, std::ptr::null_mut::<u8>())?;
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn report_event(_event: u64) {}
pub fn grant_root() -> std::io::Result<()> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool {
rustix::process::ksu_check_kernel_safemode()
fn report_event(event: u32) {
let mut cmd = ReportEventCmd { event };
let _ = ksuctl(KSU_IOCTL_REPORT_EVENT, &mut cmd as *mut _);
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool {
false
}
fn report_event(_event: u32) {}
pub fn report_post_fs_data() {
report_event(EVENT_POST_FS_DATA);
@ -41,3 +206,91 @@ pub fn report_boot_complete() {
pub fn report_module_mounted() {
report_event(EVENT_MODULE_MOUNTED);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn check_kernel_safemode() -> bool {
let mut cmd = CheckSafemodeCmd { in_safe_mode: 0 };
let _ = ksuctl(KSU_IOCTL_CHECK_SAFEMODE, &mut cmd as *mut _);
cmd.in_safe_mode != 0
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn check_kernel_safemode() -> bool {
false
}
pub fn set_sepolicy(cmd: &SetSepolicyCmd) -> std::io::Result<()> {
let mut ioctl_cmd = *cmd;
ksuctl(KSU_IOCTL_SET_SEPOLICY, &mut ioctl_cmd as *mut _)?;
Ok(())
}
/// Get feature value and support status from kernel
/// Returns (value, supported)
pub fn get_feature(feature_id: u32) -> std::io::Result<(u64, bool)> {
let mut cmd = GetFeatureCmd {
feature_id,
value: 0,
supported: 0,
};
ksuctl(KSU_IOCTL_GET_FEATURE, &mut cmd as *mut _)?;
Ok((cmd.value, cmd.supported != 0))
}
/// Set feature value in kernel
pub fn set_feature(feature_id: u32, value: u64) -> std::io::Result<()> {
let mut cmd = SetFeatureCmd { feature_id, value };
ksuctl(KSU_IOCTL_SET_FEATURE, &mut cmd as *mut _)?;
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_wrapped_fd(fd: RawFd) -> std::io::Result<RawFd> {
let mut cmd = GetWrapperFdCmd { fd, flags: 0 };
let result = ksuctl(KSU_IOCTL_GET_WRAPPER_FD, &mut cmd as *mut _)?;
Ok(result)
}
/// Get mark status for a process (pid=0 returns total marked count)
pub fn mark_get(pid: i32) -> std::io::Result<u32> {
let mut cmd = ManageMarkCmd {
operation: KSU_MARK_GET,
pid,
result: 0,
};
ksuctl(KSU_IOCTL_MANAGE_MARK, &mut cmd as *mut _)?;
Ok(cmd.result)
}
/// Mark a process (pid=0 marks all processes)
pub fn mark_set(pid: i32) -> std::io::Result<()> {
let mut cmd = ManageMarkCmd {
operation: KSU_MARK_MARK,
pid,
result: 0,
};
ksuctl(KSU_IOCTL_MANAGE_MARK, &mut cmd as *mut _)?;
Ok(())
}
/// Unmark a process (pid=0 unmarks all processes)
pub fn mark_unset(pid: i32) -> std::io::Result<()> {
let mut cmd = ManageMarkCmd {
operation: KSU_MARK_UNMARK,
pid,
result: 0,
};
ksuctl(KSU_IOCTL_MANAGE_MARK, &mut cmd as *mut _)?;
Ok(())
}
/// Refresh mark for all running processes
pub fn mark_refresh() -> std::io::Result<()> {
let mut cmd = ManageMarkCmd {
operation: KSU_MARK_REFRESH,
pid: 0,
result: 0,
};
ksuctl(KSU_IOCTL_MANAGE_MARK, &mut cmd as *mut _)?;
Ok(())
}

View file

@ -1,22 +1,28 @@
use crate::defs::{DISABLE_FILE_NAME, KSU_MOUNT_SOURCE, MODULE_DIR, SKIP_MOUNT_FILE_NAME};
use crate::magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout};
use crate::restorecon::{lgetfilecon, lsetfilecon};
use crate::utils::{ensure_dir_exists, get_work_dir};
use std::{
cmp::PartialEq,
collections::{HashMap, hash_map::Entry},
fs::{self, DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link},
os::unix::fs::{FileTypeExt, symlink},
path::{Path, PathBuf},
};
use anyhow::{Context, Result, bail};
use extattr::lgetxattr;
use rustix::fs::{
Gid, MetadataExt, Mode, MountFlags, MountPropagationFlags, Uid, UnmountFlags, bind_mount,
chmod, chown, mount, move_mount, remount, unmount,
use rustix::{
fs::{
Gid, MetadataExt, Mode, MountFlags, MountPropagationFlags, Uid, UnmountFlags, bind_mount,
chmod, chown, mount, move_mount, remount, unmount,
},
mount::mount_change,
path::Arg,
};
use crate::{
defs::{DISABLE_FILE_NAME, KSU_MOUNT_SOURCE, MODULE_DIR, SKIP_MOUNT_FILE_NAME},
magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout},
restorecon::{lgetfilecon, lsetfilecon},
utils::ensure_dir_exists,
};
use rustix::mount::mount_change;
use rustix::path::Arg;
use std::cmp::PartialEq;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::fs;
use std::fs::{DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link};
use std::os::unix::fs::{FileTypeExt, symlink};
use std::path::{Path, PathBuf};
const REPLACE_DIR_XATTR: &str = "trusted.overlay.opaque";
@ -54,7 +60,10 @@ struct Node {
}
impl Node {
fn collect_module_files<T: AsRef<Path>>(&mut self, module_dir: T) -> Result<bool> {
fn collect_module_files<P>(&mut self, module_dir: P) -> Result<bool>
where
P: AsRef<Path>,
{
let dir = module_dir.as_ref();
let mut has_file = false;
for entry in dir.read_dir()?.flatten() {
@ -77,7 +86,10 @@ impl Node {
Ok(has_file)
}
fn new_root<T: ToString>(name: T) -> Self {
fn new_root<T>(name: T) -> Self
where
T: ToString,
{
Node {
name: name.to_string(),
file_type: Directory,
@ -88,7 +100,10 @@ impl Node {
}
}
fn new_module<T: ToString>(name: T, entry: &DirEntry) -> Option<Self> {
fn new_module<T>(name: T, entry: &DirEntry) -> Option<Self>
where
T: ToString,
{
if let Ok(metadata) = entry.metadata() {
let path = entry.path();
let file_type = if metadata.file_type().is_char_device() && metadata.rdev() == 0 {
@ -98,12 +113,11 @@ impl Node {
};
if let Some(file_type) = file_type {
let mut replace = false;
if file_type == Directory {
if let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR) {
if String::from_utf8_lossy(&v) == "y" {
replace = true;
}
}
if file_type == Directory
&& let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR)
&& String::from_utf8_lossy(&v) == "y"
{
replace = true;
}
return Some(Node {
name: name.to_string(),
@ -169,7 +183,10 @@ fn collect_module_files() -> Result<Option<Node>> {
}
}
fn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst) -> Result<()> {
fn clone_symlink<P>(src: P, dst: P) -> Result<()>
where
P: AsRef<Path>,
{
let src_symlink = read_link(src.as_ref())?;
symlink(&src_symlink, dst.as_ref())?;
lsetfilecon(dst.as_ref(), lgetfilecon(src.as_ref())?.as_str())?;
@ -182,11 +199,10 @@ fn clone_symlink<Src: AsRef<Path>, Dst: AsRef<Path>>(src: Src, dst: Dst) -> Resu
Ok(())
}
fn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(
path: P,
work_dir_path: WP,
entry: &DirEntry,
) -> Result<()> {
fn mount_mirror<P>(path: P, work_dir_path: P, entry: &DirEntry) -> Result<()>
where
P: AsRef<Path>,
{
let path = path.as_ref().join(entry.file_name());
let work_dir_path = work_dir_path.as_ref().join(entry.file_name());
let file_type = entry.file_type()?;
@ -231,12 +247,11 @@ fn mount_mirror<P: AsRef<Path>, WP: AsRef<Path>>(
Ok(())
}
fn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(
path: P,
work_dir_path: WP,
current: Node,
has_tmpfs: bool,
) -> Result<()> {
fn do_magic_mount<P, WP>(path: P, work_dir_path: WP, current: Node, has_tmpfs: bool) -> Result<()>
where
P: AsRef<Path>,
WP: AsRef<Path>,
{
let mut current = current;
let path = path.as_ref().join(&current.name);
let work_dir_path = work_dir_path.as_ref().join(&current.name);
@ -430,10 +445,10 @@ fn do_magic_mount<P: AsRef<Path>, WP: AsRef<Path>>(
Ok(())
}
pub fn magic_mount() -> Result<()> {
pub fn magic_mount(tmp_path: &String) -> Result<()> {
if let Some(root) = collect_module_files()? {
log::debug!("collected: {:#?}", root);
let tmp_dir = PathBuf::from(get_work_dir());
let tmp_dir = Path::new(tmp_path).join("workdir");
ensure_dir_exists(&tmp_dir)?;
mount(KSU_MOUNT_SOURCE, &tmp_dir, "tmpfs", MountFlags::empty(), "").context("mount tmp")?;
mount_change(&tmp_dir, MountPropagationFlags::PRIVATE).context("make tmp private")?;

View file

@ -4,7 +4,9 @@ mod boot_patch;
mod cli;
mod debug;
mod defs;
mod feature;
mod init_event;
#[cfg(target_arch = "aarch64")]
mod kpm;
mod ksucalls;
#[cfg(target_os = "android")]
@ -14,8 +16,9 @@ mod profile;
mod restorecon;
mod sepolicy;
mod su;
mod utils;
mod uid_scanner;
mod umount_manager;
mod utils;
fn main() -> anyhow::Result<()> {
cli::run()

View file

@ -1,18 +1,6 @@
#[allow(clippy::wildcard_imports)]
use crate::utils::*;
use crate::{
assets, defs, ksucalls,
restorecon::{restore_syscon, setsyscon},
sepolicy,
};
use anyhow::{Context, Result, anyhow, bail, ensure};
use const_format::concatcp;
use is_executable::is_executable;
use java_properties::PropertiesIter;
use log::{info, warn};
use std::fs::{copy, rename};
#[cfg(unix)]
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
use std::{
collections::HashMap,
env::var as env_var,
@ -22,11 +10,23 @@ use std::{
process::Command,
str::FromStr,
};
use anyhow::{Context, Result, anyhow, bail, ensure};
use const_format::concatcp;
use is_executable::is_executable;
use java_properties::PropertiesIter;
use log::{info, warn};
use zip_extensions::zip_extract_file_to_memory;
use crate::defs::{MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME};
#[cfg(unix)]
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
#[allow(clippy::wildcard_imports)]
use crate::{
assets,
defs::{self, MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME},
ksucalls,
restorecon::{restore_syscon, setsyscon},
sepolicy,
utils::*,
};
const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
const INSTALL_MODULE_SCRIPT: &str = concatcp!(
@ -256,10 +256,10 @@ pub fn prune_modules() -> Result<()> {
info!("remove module: {}", module.display());
let uninstaller = module.join("uninstall.sh");
if uninstaller.exists() {
if let Err(e) = exec_script(uninstaller, true) {
warn!("Failed to exec uninstaller: {}", e);
}
if uninstaller.exists()
&& let Err(e) = exec_script(uninstaller, true)
{
warn!("Failed to exec uninstaller: {}", e);
}
if let Err(e) = remove_dir_all(module) {
@ -283,10 +283,10 @@ pub fn handle_updated_modules() -> Result<()> {
if let Some(name) = module.file_name() {
let old_dir = modules_root.join(name);
if old_dir.exists() {
if let Err(e) = remove_dir_all(&old_dir) {
log::error!("Failed to remove old {}: {}", old_dir.display(), e);
}
if old_dir.exists()
&& let Err(e) = remove_dir_all(&old_dir)
{
log::error!("Failed to remove old {}: {}", old_dir.display(), e);
}
if let Err(e) = rename(module, &old_dir) {
log::error!("Failed to move new module {}: {}", module.display(), e);
@ -434,6 +434,28 @@ fn mark_all_modules(flag_file: &str) -> Result<()> {
Ok(())
}
/// Read module.prop from the given module path and return as a HashMap
pub fn read_module_prop(module_path: &Path) -> Result<HashMap<String, String>> {
let module_prop = module_path.join("module.prop");
ensure!(
module_prop.exists(),
"module.prop not found in {}",
module_path.display()
);
let content = std::fs::read(&module_prop)
.with_context(|| format!("Failed to read module.prop: {}", module_prop.display()))?;
let mut prop_map: HashMap<String, String> = HashMap::new();
PropertiesIter::new_with_encoding(Cursor::new(content), encoding_rs::UTF_8)
.read_into(|k, v| {
prop_map.insert(k, v);
})
.with_context(|| format!("Failed to parse module.prop: {}", module_prop.display()))?;
Ok(prop_map)
}
fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
// first check enabled modules
let dir = std::fs::read_dir(path);
@ -446,22 +468,19 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
for entry in dir.flatten() {
let path = entry.path();
info!("path: {}", path.display());
let module_prop = path.join("module.prop");
if !module_prop.exists() {
if !path.join("module.prop").exists() {
continue;
}
let content = std::fs::read(&module_prop);
let Ok(content) = content else {
warn!("Failed to read file: {}", module_prop.display());
continue;
let mut module_prop_map = match read_module_prop(&path) {
Ok(prop) => prop,
Err(e) => {
warn!("Failed to read module.prop for {}: {}", path.display(), e);
continue;
}
};
let mut module_prop_map: HashMap<String, String> = HashMap::new();
let encoding = encoding_rs::UTF_8;
let result =
PropertiesIter::new_with_encoding(Cursor::new(content), encoding).read_into(|k, v| {
module_prop_map.insert(k, v);
});
// If id is missing or empty, use directory name as fallback
let dir_id = entry.file_name().to_string_lossy().to_string();
module_prop_map.insert("dir_id".to_owned(), dir_id.clone());
@ -483,10 +502,6 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
module_prop_map.insert("web".to_owned(), web.to_string());
module_prop_map.insert("action".to_owned(), action.to_string());
if result.is_err() {
warn!("Failed to parse module.prop: {}", module_prop.display());
continue;
}
modules.push(module_prop_map);
}
@ -498,3 +513,51 @@ pub fn list_modules() -> Result<()> {
println!("{}", serde_json::to_string_pretty(&modules)?);
Ok(())
}
/// Get all managed features from active modules
/// Modules can specify managedFeatures in their module.prop
/// Format: managedFeatures=feature1,feature2,feature3
/// Returns: HashMap<ModuleId, Vec<ManagedFeature>>
pub fn get_managed_features() -> Result<HashMap<String, Vec<String>>> {
let mut managed_features_map: HashMap<String, Vec<String>> = HashMap::new();
foreach_active_module(|module_path| {
let prop_map = match read_module_prop(module_path) {
Ok(prop) => prop,
Err(e) => {
warn!(
"Failed to read module.prop for {}: {}",
module_path.display(),
e
);
return Ok(());
}
};
if let Some(features_str) = prop_map.get("managedFeatures") {
let module_id = prop_map
.get("id")
.map(|s| s.to_string())
.unwrap_or_else(|| "unknown".to_string());
info!("Module {} manages features: {}", module_id, features_str);
let mut feature_list = Vec::new();
for feature in features_str.split(',') {
let feature = feature.trim();
if !feature.is_empty() {
info!(" - Adding managed feature: {}", feature);
feature_list.push(feature.to_string());
}
}
if !feature_list.is_empty() {
managed_features_map.insert(module_id, feature_list);
}
}
Ok(())
})?;
Ok(managed_features_map)
}

View file

@ -1,8 +1,9 @@
use crate::utils::ensure_dir_exists;
use crate::{defs, sepolicy};
use anyhow::{Context, Result};
use std::path::Path;
use anyhow::{Context, Result};
use crate::{defs, sepolicy, utils::ensure_dir_exists};
pub fn set_sepolicy(pkg: String, policy: String) -> Result<()> {
ensure_dir_exists(defs::PROFILE_SELINUX_DIR)?;
let policy_file = Path::new(defs::PROFILE_SELINUX_DIR).join(pkg);

View file

@ -1,12 +1,13 @@
use crate::defs;
use anyhow::Result;
use jwalk::{Parallelism::Serial, WalkDir};
use std::path::Path;
use anyhow::Result;
#[cfg(any(target_os = "linux", target_os = "android"))]
use anyhow::{Context, Ok};
#[cfg(any(target_os = "linux", target_os = "android"))]
use extattr::{Flags as XattrFlags, lsetxattr};
use jwalk::{Parallelism::Serial, WalkDir};
use crate::defs;
pub const SYSTEM_CON: &str = "u:object_r:system_file:s0";
pub const ADB_CON: &str = "u:object_r:adb_data_file:s0";
@ -63,12 +64,11 @@ pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
fn restore_modules_con<P: AsRef<Path>>(dir: P) -> Result<()> {
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
if let Result::Ok(con) = lgetfilecon(&path) {
if con == ADB_CON || con == UNLABEL_CON || con.is_empty() {
lsetfilecon(&path, SYSTEM_CON)?;
}
}
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path())
&& let Result::Ok(con) = lgetfilecon(&path)
&& (con == ADB_CON || con == UNLABEL_CON || con.is_empty())
{
lsetfilecon(&path, SYSTEM_CON)?;
}
}
Ok(())

View file

@ -1,3 +1,5 @@
use std::{ffi, path::Path, vec};
use anyhow::{Result, bail};
use derive_new::new;
use nom::{
@ -7,7 +9,6 @@ use nom::{
character::complete::{space0, space1},
combinator::map,
};
use std::{ffi, path::Path, vec};
type SeObject<'a> = Vec<&'a str>;
@ -19,7 +20,7 @@ fn parse_single_word(input: &str) -> IResult<&str, &str> {
take_while1(is_sepolicy_char).parse(input)
}
fn parse_bracket_objs<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, (_, words, _)) = (
tag("{"),
take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()),
@ -29,12 +30,12 @@ fn parse_bracket_objs<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
Ok((input, words.split_whitespace().collect()))
}
fn parse_single_obj<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
fn parse_single_obj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, word) = take_while1(is_sepolicy_char).parse(input)?;
Ok((input, vec![word]))
}
fn parse_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
fn parse_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, _) = tag("*").parse(input)?;
Ok((input, vec!["*"]))
}
@ -42,12 +43,12 @@ fn parse_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
// 1. a single sepolicy word
// 2. { obj1 obj2 obj3 ...}
// 3. *
fn parse_seobj<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
fn parse_seobj(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?;
Ok((input, strs))
}
fn parse_seobj_no_star<'a>(input: &'a str) -> IResult<&'a str, SeObject<'a>> {
fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject<'_>> {
let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?;
Ok((input, strs))
}
@ -696,10 +697,15 @@ fn apply_one_rule<'a>(statement: &'a PolicyStatement<'a>, strict: bool) -> Resul
let policies: Vec<AtomicStatement> = statement.try_into()?;
for policy in policies {
if !rustix::process::ksu_set_policy(&FfiPolicy::from(policy)) {
log::warn!("apply rule: {statement:?} failed.");
let ffi_policy = FfiPolicy::from(policy);
let cmd = crate::ksucalls::SetSepolicyCmd {
cmd: 0,
arg: &ffi_policy as *const _ as u64,
};
if let Err(e) = crate::ksucalls::set_sepolicy(&cmd) {
log::warn!("apply rule {:?} failed: {}", statement, e);
if strict {
return Err(anyhow::anyhow!("apply rule {:?} failed.", statement));
return Err(anyhow::anyhow!("apply rule {:?} failed: {}", statement, e));
}
}
}

View file

@ -1,15 +1,17 @@
use anyhow::{Ok, Result};
use getopts::Options;
use std::env;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::{ffi::CStr, process::Command};
use crate::{
defs,
utils::{self, umask},
};
use anyhow::{Context, Ok, Result, bail};
use getopts::Options;
use libc::c_int;
use log::{error, warn};
#[cfg(unix)]
use std::os::unix::process::CommandExt;
use std::{env, ffi::CStr, path::PathBuf, process::Command};
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::ksucalls::get_wrapped_fd;
#[cfg(any(target_os = "linux", target_os = "android"))]
use rustix::{
@ -19,7 +21,7 @@ use rustix::{
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn grant_root(global_mnt: bool) -> Result<()> {
rustix::process::ksu_grant_root()?;
crate::ksucalls::grant_root()?;
let mut command = Command::new("sh");
let command = unsafe {
@ -64,6 +66,29 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) {
}
}
#[cfg(target_os = "android")]
fn wrap_tty(fd: c_int) {
let inner_fn = move || -> Result<()> {
if unsafe { libc::isatty(fd) != 1 } {
warn!("not a tty: {fd}");
return Ok(());
}
let new_fd = get_wrapped_fd(fd).context("get_wrapped_fd")?;
if unsafe { libc::dup2(new_fd, fd) } == -1 {
bail!("dup {new_fd} -> {fd} errno: {}", unsafe {
*libc::__errno()
});
} else {
unsafe { libc::close(new_fd) };
Ok(())
}
};
if let Err(e) = inner_fn() {
error!("wrap tty {fd}: {e:?}");
}
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn root_shell() -> Result<()> {
unimplemented!()
@ -124,6 +149,7 @@ pub fn root_shell() -> Result<()> {
"Specify a supplementary group. The first specified supplementary group is also used as a primary group if the option -g is not specified.",
"GROUP",
);
opts.optflag("W", "no-wrapper", "don't use ksu fd wrapper");
// Replace -cn with -z, -mm with -M for supporting getopt_long
let args = args
@ -167,6 +193,7 @@ pub fn root_shell() -> Result<()> {
let mut is_login = matches.opt_present("l");
let preserve_env = matches.opt_present("p");
let mount_master = matches.opt_present("M");
let use_fd_wrapper = !matches.opt_present("W");
let groups = matches
.opt_strs("G")
@ -265,6 +292,13 @@ pub fn root_shell() -> Result<()> {
let _ = utils::switch_mnt_ns(1);
}
#[cfg(target_os = "android")]
if use_fd_wrapper {
wrap_tty(0);
wrap_tty(1);
wrap_tty(2);
}
set_identity(uid, gid, &groups);
Result::Ok(())

View file

@ -1,24 +1,24 @@
use anyhow::Result;
use log::{info, warn};
use std::{
fs,
io::Write,
os::unix::{
fs::{symlink, PermissionsExt},
fs::{PermissionsExt, symlink},
process::CommandExt,
},
path::Path,
process::{Command, Stdio},
};
pub fn start_uid_scanner_daemon() -> Result<()> {
const SCANNER_PATH: &str = "/data/adb/uid_scanner";
const LINK_DIR: &str = "/data/adb/ksu/bin";
const LINK_PATH: &str = "/data/adb/ksu/bin/uid_scanner";
const SERVICE_DIR: &str = "/data/adb/service.d";
const SERVICE_PATH: &str = "/data/adb/service.d/uid_scanner.sh";
use anyhow::Result;
use log::{info, warn};
if !Path::new(SCANNER_PATH).exists() {
const SCANNER_PATH: &str = "/data/adb/uid_scanner";
const LINK_DIR: &str = "/data/adb/ksu/bin";
const LINK_PATH: &str = "/data/adb/ksu/bin/uid_scanner";
const SERVICE_DIR: &str = "/data/adb/service.d";
const SERVICE_PATH: &str = "/data/adb/service.d/uid_scanner.sh";
pub fn start_uid_scanner_daemon() -> Result<()> {
if !fs::exists(SCANNER_PATH)? {
warn!("uid scanner binary not found at {}", SCANNER_PATH);
return Ok(());
}
@ -31,7 +31,7 @@ pub fn start_uid_scanner_daemon() -> Result<()> {
{
if let Err(e) = fs::create_dir_all(LINK_DIR) {
warn!("failed to create {}: {}", LINK_DIR, e);
} else if !Path::new(LINK_PATH).exists() {
} else if !fs::exists(LINK_PATH)? {
match symlink(SCANNER_PATH, LINK_PATH) {
Ok(_) => info!("created symlink {} -> {}", SCANNER_PATH, LINK_PATH),
Err(e) => warn!("failed to create symlink: {}", e),
@ -41,13 +41,11 @@ pub fn start_uid_scanner_daemon() -> Result<()> {
if let Err(e) = fs::create_dir_all(SERVICE_DIR) {
warn!("failed to create {}: {}", SERVICE_DIR, e);
} else if !Path::new(SERVICE_PATH).exists() {
let content = r#"#!/system/bin/sh
# KSU uid_scanner auto-restart script
until [ -d "/sdcard/Android" ]; do sleep 1; done
sleep 10
/data/adb/uid_scanner restart
"#;
}
if !fs::exists(SERVICE_PATH)? {
let content = include_str!("uid_scanner.sh");
match fs::OpenOptions::new()
.write(true)
.create_new(true)

View file

@ -0,0 +1,5 @@
#!/system/bin/sh
# KSU uid_scanner auto-restart script
until [ -d "/sdcard/Android" ]; do sleep 1; done
sleep 10
/data/adb/uid_scanner restart

View file

@ -0,0 +1,344 @@
use anyhow::{Context, Result, anyhow};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use crate::ksucalls::ksuctl;
const MAGIC_NUMBER_HEADER: &[u8; 4] = b"KUMT";
const MAGIC_VERSION: u32 = 1;
const CONFIG_FILE: &str = "/data/adb/ksu/.umount";
const KSU_IOCTL_UMOUNT_MANAGER: u32 = 0xc0004b6b; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 107, 0)
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct UmountEntry {
pub path: String,
pub check_mnt: bool,
pub flags: i32,
pub is_default: bool,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct UmountConfig {
pub entries: Vec<UmountEntry>,
}
pub struct UmountManager {
config: UmountConfig,
config_path: PathBuf,
defaults: Vec<UmountEntry>,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct UmountManagerCmd {
pub operation: u32,
pub path: [u8; 256],
pub check_mnt: u8,
pub flags: i32,
pub count: u32,
pub entries_ptr: u64,
}
impl Default for UmountManagerCmd {
fn default() -> Self {
UmountManagerCmd {
operation: 0,
path: [0; 256],
check_mnt: 0,
flags: 0,
count: 0,
entries_ptr: 0,
}
}
}
impl UmountManager {
pub fn new(config_path: Option<PathBuf>) -> Result<Self> {
let path = config_path.unwrap_or_else(|| PathBuf::from(CONFIG_FILE));
let config = if path.exists() {
Self::load_config(&path)?
} else {
UmountConfig {
entries: Vec::new(),
}
};
Ok(UmountManager {
config,
config_path: path,
defaults: Vec::new(),
})
}
fn load_config(path: &Path) -> Result<UmountConfig> {
let data = fs::read(path).context("Failed to read config file")?;
if data.len() < 8 {
return Err(anyhow!("Invalid config file: too small"));
}
let header = &data[0..4];
if header != MAGIC_NUMBER_HEADER {
return Err(anyhow!("Invalid config file: wrong magic number"));
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
if version != MAGIC_VERSION {
return Err(anyhow!("Unsupported config version: {}", version));
}
let json_data = &data[8..];
let config: UmountConfig =
serde_json::from_slice(json_data).context("Failed to parse config JSON")?;
Ok(config)
}
pub fn save_config(&self) -> Result<()> {
let dir = self.config_path.parent().unwrap();
fs::create_dir_all(dir).context("Failed to create config directory")?;
let mut data = Vec::new();
data.extend_from_slice(MAGIC_NUMBER_HEADER);
data.extend_from_slice(&MAGIC_VERSION.to_le_bytes());
let json = serde_json::to_vec(&self.config).context("Failed to serialize config")?;
data.extend_from_slice(&json);
fs::write(&self.config_path, &data).context("Failed to write config file")?;
Ok(())
}
pub fn add_entry(&mut self, path: &str, check_mnt: bool, flags: i32) -> Result<()> {
let exists = self
.defaults
.iter()
.chain(&self.config.entries)
.any(|e| e.path == path);
if exists {
return Err(anyhow!("Entry already exists: {}", path));
}
let is_default = Self::get_default_paths().iter().any(|e| e.path == path);
let entry = UmountEntry {
path: path.to_string(),
check_mnt,
flags,
is_default,
};
self.config.entries.push(entry);
Ok(())
}
pub fn remove_entry(&mut self, path: &str) -> Result<()> {
let entry = self.config.entries.iter().find(|e| e.path == path);
if let Some(entry) = entry {
if entry.is_default {
return Err(anyhow!("Cannot remove default entry: {}", path));
}
} else {
return Err(anyhow!("Entry not found: {}", path));
}
self.config.entries.retain(|e| e.path != path);
Ok(())
}
pub fn list_entries(&self) -> Vec<UmountEntry> {
let mut all = self.defaults.clone();
all.extend(self.config.entries.iter().cloned());
all
}
pub fn clear_custom_entries(&mut self) -> Result<()> {
self.config.entries.retain(|e| e.is_default);
Ok(())
}
pub fn get_default_paths() -> Vec<UmountEntry> {
vec![
UmountEntry {
path: "/odm".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/system".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/vendor".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/product".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/system_ext".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/data/adb/modules".to_string(),
check_mnt: false,
flags: -1, // MNT_DETACH
is_default: true,
},
UmountEntry {
path: "/debug_ramdisk".to_string(),
check_mnt: false,
flags: -1, // MNT_DETACH
is_default: true,
},
]
}
pub fn init_defaults(&mut self) -> Result<()> {
self.defaults = Self::get_default_paths();
Ok(())
}
pub fn apply_to_kernel(&self) -> Result<()> {
for entry in &self.defaults {
let _ = Self::kernel_add_entry(entry);
}
for entry in &self.config.entries {
Self::kernel_add_entry(entry)?;
}
Ok(())
}
fn kernel_add_entry(entry: &UmountEntry) -> Result<()> {
let mut cmd = UmountManagerCmd {
operation: 0,
check_mnt: entry.check_mnt as u8,
flags: entry.flags,
..Default::default()
};
let path_bytes = entry.path.as_bytes();
if path_bytes.len() >= cmd.path.len() {
return Err(anyhow!("Path too long: {}", entry.path));
}
cmd.path[..path_bytes.len()].copy_from_slice(path_bytes);
umount_manager_ioctl(&cmd).context(format!("Failed to add entry: {}", entry.path))?;
Ok(())
}
}
pub fn init_umount_manager() -> Result<UmountManager> {
let mut manager = UmountManager::new(None)?;
manager.init_defaults()?;
if !Path::new(CONFIG_FILE).exists() {
manager.save_config()?;
}
Ok(manager)
}
pub fn add_umount_path(path: &str, check_mnt: bool, flags: i32) -> Result<()> {
let mut manager = init_umount_manager()?;
manager.add_entry(path, check_mnt, flags)?;
manager.save_config()?;
println!("✓ Added umount path: {}", path);
Ok(())
}
pub fn remove_umount_path(path: &str) -> Result<()> {
let mut manager = init_umount_manager()?;
manager.remove_entry(path)?;
manager.save_config()?;
println!("✓ Removed umount path: {}", path);
Ok(())
}
pub fn list_umount_paths() -> Result<()> {
let manager = init_umount_manager()?;
let entries = manager.list_entries();
if entries.is_empty() {
println!("No umount paths configured");
return Ok(());
}
println!(
"{:<30} {:<12} {:<8} {:<10}",
"Path", "CheckMnt", "Flags", "Default"
);
println!("{}", "=".repeat(60));
for entry in entries {
println!(
"{:<30} {:<12} {:<8} {:<10}",
entry.path,
entry.check_mnt,
entry.flags,
if entry.is_default { "Yes" } else { "No" }
);
}
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn umount_manager_ioctl(cmd: &UmountManagerCmd) -> std::io::Result<()> {
let mut ioctl_cmd = *cmd;
ksuctl(KSU_IOCTL_UMOUNT_MANAGER, &mut ioctl_cmd as *mut _)?;
Ok(())
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn umount_manager_ioctl(_cmd: &UmountManagerCmd) -> std::io::Result<()> {
Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
}
pub fn clear_custom_paths() -> Result<()> {
let mut manager = init_umount_manager()?;
manager.clear_custom_entries()?;
manager.save_config()?;
println!("✓ Cleared all custom paths");
Ok(())
}
pub fn save_umount_config() -> Result<()> {
let manager = init_umount_manager()?;
manager.save_config()?;
println!("✓ Configuration saved to: {}", CONFIG_FILE);
Ok(())
}
pub fn load_and_apply_config() -> Result<()> {
let manager = init_umount_manager()?;
manager.apply_to_kernel()?;
println!("✓ Configuration applied to kernel");
Ok(())
}
pub fn apply_config_to_kernel() -> Result<()> {
let manager = init_umount_manager()?;
manager.apply_to_kernel()?;
println!(
"✓ Applied {} entries to kernel",
manager.list_entries().len()
);
Ok(())
}

View file

@ -1,28 +1,25 @@
use anyhow::{Context, Error, Ok, Result, bail};
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use std::{
fs::{self, File, OpenOptions, create_dir_all, remove_file, write},
fs::{Permissions, set_permissions},
io::{
ErrorKind::{AlreadyExists, NotFound},
Write,
},
path::Path,
path::{Path, PathBuf},
process::Command,
};
use crate::{assets, boot_patch, defs, ksucalls, module, restorecon};
#[allow(unused_imports)]
use std::fs::{Permissions, set_permissions};
#[cfg(unix)]
use std::os::unix::prelude::PermissionsExt;
use std::path::PathBuf;
use anyhow::{Context, Error, Ok, Result, bail};
#[cfg(any(target_os = "linux", target_os = "android"))]
use rustix::{
process,
thread::{LinkNameSpaceType, move_into_link_name_space},
};
use crate::{assets, boot_patch, defs, ksucalls, module, restorecon};
pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
let path = dir.as_ref();
log::debug!("ensure_clean_dir: {}", path.display());
@ -48,13 +45,11 @@ pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
}
pub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {
let result = create_dir_all(&dir).map_err(Error::from);
if dir.as_ref().is_dir() {
result
} else if result.is_ok() {
bail!("{} is not a regular directory", dir.as_ref().display())
let result = create_dir_all(&dir);
if dir.as_ref().is_dir() && result.is_ok() {
Ok(())
} else {
result
bail!("{} is not a regular directory", dir.as_ref().display())
}
}
@ -74,11 +69,11 @@ pub fn ensure_binary<T: AsRef<Path>>(
)
})?)?;
if let Err(e) = remove_file(path.as_ref()) {
if e.kind() != NotFound {
return Err(Error::from(e))
.with_context(|| format!("failed to unlink {}", path.as_ref().display()));
}
if let Err(e) = remove_file(path.as_ref())
&& e.kind() != NotFound
{
return Err(Error::from(e))
.with_context(|| format!("failed to unlink {}", path.as_ref().display()));
}
write(&path, contents)?;
@ -145,7 +140,7 @@ fn switch_cgroup(grp: &str, pid: u32) {
let fp = OpenOptions::new().append(true).open(path);
if let std::result::Result::Ok(mut fp) = fp {
let _ = writeln!(fp, "{pid}");
let _ = write!(fp, "{pid}");
}
}
@ -186,7 +181,7 @@ fn is_ok_empty(dir: &str) -> bool {
}
}
pub fn get_tmp_path() -> String {
pub fn find_tmp_path() -> String {
let dirs = ["/debug_ramdisk", "/patch_hw", "/oem", "/root", "/sbin"];
// find empty directory
@ -198,11 +193,6 @@ pub fn get_tmp_path() -> String {
"".to_string()
}
pub fn get_work_dir() -> String {
let tmp_path = get_tmp_path();
format!("{}/workdir/", tmp_path)
}
#[cfg(target_os = "android")]
fn link_ksud_to_bin() -> Result<()> {
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
@ -215,7 +205,10 @@ fn link_ksud_to_bin() -> Result<()> {
pub fn install(magiskboot: Option<PathBuf>) -> Result<()> {
ensure_dir_exists(defs::ADB_DIR)?;
std::fs::copy("/proc/self/exe", defs::DAEMON_PATH)?;
std::fs::copy(
std::env::current_exe().with_context(|| "Failed to get self exe path")?,
defs::DAEMON_PATH,
)?;
restorecon::lsetfilecon(defs::DAEMON_PATH, restorecon::ADB_CON)?;
// install binary assets
assets::ensure_binaries(false).with_context(|| "Failed to extract assets")?;
@ -245,7 +238,7 @@ pub fn uninstall(magiskboot_path: Option<PathBuf>) -> Result<()> {
boot_patch::restore(None, magiskboot_path, true)?;
println!("- Uninstall KernelSU manager..");
Command::new("pm")
.args(["uninstall", "me.weishu.kernelsu"])
.args(["uninstall", "com.sukisu.ultra"])
.spawn()?;
println!("- Rebooting in 5 seconds..");
std::thread::sleep(std::time::Duration::from_secs(5));

View file

@ -1,11 +1,54 @@
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <limits.h>
#include <errno.h>
#include <stdlib.h>
#include <termios.h>
// This is a simple example. If you want a full-featured "su", please use "/data/adb/ksud debug su".
int main(){
int32_t result = 0;
prctl(0xdeadbeef, 0, 0, 0, &result);
system("/system/bin/sh");
return 0;
#define KERNEL_SU_OPTION 0xDEADBEEF
#define CMD_GRANT_ROOT 0
#define CMD_ENABLE_SU 15
int main(int argc, char **argv, char **envp) {
unsigned long result = 0;
if (argc >= 2 && strcmp(argv[1], "--disable-sucompat") == 0) {
prctl(KERNEL_SU_OPTION, CMD_ENABLE_SU, 0L, 0L, (unsigned long)&result);
return 0;
}
prctl(KERNEL_SU_OPTION, CMD_GRANT_ROOT, 0L, 0L, (unsigned long)&result);
if (result != KERNEL_SU_OPTION) {
const char *error = "Access Denied: sucompat not permitted\n";
write(STDERR_FILENO, error, strlen(error));
return 1;
}
struct termios term;
if (ioctl(STDIN_FILENO, TCGETS, &term) == 0) {
char tty_path[PATH_MAX];
ssize_t len = readlink("/proc/self/fd/0", tty_path, sizeof(tty_path) - 1);
if (len > 0) {
tty_path[len] = '\0';
const char *selinux_ctx = "u:object_r:devpts:s0";
setxattr(tty_path, "security.selinux", selinux_ctx, strlen(selinux_ctx) + 1, 0);
}
}
const char *default_args[] = { "/system/bin/su", NULL };
if (argc < 1 || !argv) {
argv = (char **)default_args;
} else {
argv[0] = "/system/bin/su";
}
execve("/data/adb/ksud", argv, envp);
const char *error = "Error: Failed to execve /data/adb/ksud\n";
write(STDERR_FILENO, error, strlen(error));
return 1;
}

View file

@ -1,2 +0,0 @@
/obj
/libs

View file

@ -1,6 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := zakozakozako
LOCAL_SRC_FILES := susfs.c
include $(BUILD_EXECUTABLE)

View file

@ -1,3 +0,0 @@
APP_ABI := arm64-v8a
APP_PLATFORM := android-24
APP_STL := none

View file

@ -1,137 +0,0 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#define KERNEL_SU_OPTION 0xDEADBEEF
// Command definitions
#define CMD_SUSFS_SHOW_VERSION 0x555e1
#define CMD_SUSFS_SHOW_ENABLED_FEATURES 0x555e2
#define CMD_SUSFS_SHOW_VARIANT 0x555e3
#define CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE 0x555e4
#define CMD_SUSFS_IS_SUS_SU_READY 0x555f0
#define CMD_SUSFS_SUS_SU 0x60000
// SUS_SU modes
#define SUS_SU_DISABLED 0
#define SUS_SU_WITH_HOOKS 2
struct st_sus_su {
int mode;
};
// Function prototypes
int enable_sus_su(int last_working_mode, int target_working_mode);
int get_sus_su_working_mode(int* mode);
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <support|version|variant|features|sus_su <0|2|mode>>\n", argv[0]);
return 1;
}
int error = -1;
if (strcmp(argv[1], "version") == 0) {
char version[16];
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_VERSION, version, NULL, &error);
printf("%s\n", error ? "Invalid" : version);
} else if (strcmp(argv[1], "variant") == 0) {
char variant[16];
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_VARIANT, variant, NULL, &error);
printf("%s\n", error ? "Invalid" : variant);
} else if (strcmp(argv[1], "features") == 0) {
char *enabled_features;
size_t bufsize = getpagesize() * 2;
enabled_features = (char *)malloc(bufsize);
if (!enabled_features) {
perror("malloc");
return -ENOMEM;
}
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_ENABLED_FEATURES, enabled_features, bufsize, &error);
if (!error) {
printf("%s", enabled_features);
} else {
printf("Invalid\n");
}
free(enabled_features);
} else if (strcmp(argv[1], "support") == 0) {
char *enabled_features;
size_t bufsize = getpagesize() * 2;
enabled_features = (char *)malloc(bufsize);
if (!enabled_features) {
perror("malloc");
return -ENOMEM;
}
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_ENABLED_FEATURES, enabled_features, bufsize, &error);
printf("%s\n", error || !strlen(enabled_features) ? "Unsupported" : "Supported");
free(enabled_features);
} else if (argc == 3 && strcmp(argv[1], "sus_su") == 0) {
int last_working_mode, target_working_mode;
char* endptr;
if (get_sus_su_working_mode(&last_working_mode)) {
return 1;
}
if (strcmp(argv[2], "mode") == 0) {
printf("%d\n", last_working_mode);
return 0;
}
target_working_mode = strtol(argv[2], &endptr, 10);
if (*endptr != '\0') {
fprintf(stderr, "Invalid argument: %s\n", argv[2]);
return 1;
}
if (target_working_mode == SUS_SU_WITH_HOOKS) {
bool is_sus_su_ready;
prctl(KERNEL_SU_OPTION, CMD_SUSFS_IS_SUS_SU_READY, &is_sus_su_ready, NULL, &error);
if (error || !is_sus_su_ready) {
printf("[-] sus_su mode %d must be run during or after service stage\n", SUS_SU_WITH_HOOKS);
return 1;
}
if (last_working_mode == SUS_SU_WITH_HOOKS) {
printf("[-] sus_su is already in mode %d\n", last_working_mode);
return 1;
}
enable_sus_su(last_working_mode, SUS_SU_WITH_HOOKS);
} else if (target_working_mode == SUS_SU_DISABLED) {
if (last_working_mode == SUS_SU_DISABLED) {
printf("[-] sus_su is already in mode %d\n", last_working_mode);
return 1;
}
enable_sus_su(last_working_mode, SUS_SU_DISABLED);
} else {
fprintf(stderr, "Invalid mode: %d\n", target_working_mode);
return 1;
}
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[1]);
return 1;
}
return 0;
}
// Helper functions
int enable_sus_su(int last_working_mode, int target_working_mode) {
struct st_sus_su info = {target_working_mode};
int error = -1;
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SUS_SU, &info, NULL, &error);
if (!error) {
printf("[+] sus_su mode %d is enabled\n", target_working_mode);
}
return error;
}
int get_sus_su_working_mode(int* mode) {
int error = -1;
prctl(KERNEL_SU_OPTION, CMD_SUSFS_SHOW_SUS_SU_WORKING_MODE, mode, NULL, &error);
return error;
}