Updated to 4.0.0
This commit is contained in:
parent
b7554a5383
commit
938198bf11
234 changed files with 21069 additions and 12710 deletions
2
userspace/kpmmgr/.gitignore
vendored
2
userspace/kpmmgr/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
/obj
|
||||
/libs
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := kpmmgr
|
||||
LOCAL_SRC_FILES := kpmmgr.c
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
APP_ABI := arm64-v8a
|
||||
APP_PLATFORM := android-24
|
||||
APP_STL := none
|
||||
|
|
@ -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;
|
||||
}
|
||||
121
userspace/ksud/Cargo.lock
generated
121
userspace/ksud/Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
404
userspace/ksud/src/feature.rs
Normal file
404
userspace/ksud/src/feature.rs
Normal 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 non‑KSU 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(())
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(¤t.name);
|
||||
let work_dir_path = work_dir_path.as_ref().join(¤t.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")?;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
5
userspace/ksud/src/uid_scanner.sh
Normal file
5
userspace/ksud/src/uid_scanner.sh
Normal 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
|
||||
344
userspace/ksud/src/umount_manager.rs
Normal file
344
userspace/ksud/src/umount_manager.rs
Normal 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(())
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
2
userspace/susfs/.gitignore
vendored
2
userspace/susfs/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
/obj
|
||||
/libs
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := zakozakozako
|
||||
LOCAL_SRC_FILES := susfs.c
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
APP_ABI := arm64-v8a
|
||||
APP_PLATFORM := android-24
|
||||
APP_STL := none
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue