ksud: replace some utils with rust libraries (#142)

This commit is contained in:
skbeh
2023-01-30 12:57:25 +08:00
committed by GitHub
parent bd6b0d3d12
commit 7785d2a3f8
9 changed files with 525 additions and 178 deletions

View File

@@ -39,8 +39,8 @@ fn set_kernel_param(size: u32, hash: u32) -> Result<()> {
}
fn get_apk_path(package_name: &str) -> Result<String> {
let cmd = format!("pm path {}", package_name);
let output = Command::new("sh").arg("-c").arg(cmd).output()?;
// `cmd package path` is not available below Android 9
let output = Command::new("pm").args(["path", package_name]).output()?;
// package:/data/app/<xxxx>/base.apk
let output = String::from_utf8_lossy(&output.stdout);
@@ -54,7 +54,7 @@ pub fn set_manager(pkg: &str) -> Result<()> {
"CONFIG_KSU_DEBUG is not enabled"
);
let path = get_apk_path(pkg).with_context(|| format!("{} not exist!", pkg))?;
let path = get_apk_path(pkg).with_context(|| format!("{pkg} does not exist!"))?;
let sign = get_apk_signature(path.as_str())?;
set_kernel_param(sign.0, sign.1)?;
Ok(())

View File

@@ -5,11 +5,11 @@ use crate::{
utils::{ensure_clean_dir, mount_image},
};
use anyhow::{bail, Result};
use subprocess::Exec;
use sys_mount::{FilesystemType, Mount, MountFlags};
fn mount_partition(partition: &str, lowerdir: &mut Vec<String>) {
if lowerdir.is_empty() {
println!("partition: {} lowerdir is empty", partition);
println!("partition: {partition} lowerdir is empty");
return;
}
@@ -19,22 +19,19 @@ fn mount_partition(partition: &str, lowerdir: &mut Vec<String>) {
return;
}
// add /partition as the lowerest dir
let lowest_dir = format!("/{}", partition);
let lowest_dir = format!("/{partition}");
lowerdir.push(lowest_dir.clone());
let lowerdir = lowerdir.join(":");
println!("partition: {} lowerdir: {}", partition, lowerdir);
println!("partition: {partition} lowerdir: {lowerdir}");
let mount_args = format!(
"mount -t overlay overlay -o ro,lowerdir={} {}",
lowerdir, lowest_dir
);
if let Ok(result) = Exec::shell(mount_args).join() {
if !result.success() {
println!("mount partition: {} overlay failed", partition);
}
} else {
println!("mount partition: {} overlay failed", partition);
if let Err(err) = Mount::builder()
.fstype(FilesystemType::from("overlay"))
.flags(MountFlags::RDONLY)
.data(&format!("lowerdir={lowerdir}"))
.mount("overlay", lowest_dir)
{
println!("mount partition: {partition} overlay failed: {err}");
}
}
@@ -136,7 +133,7 @@ pub fn on_post_data_fs() -> Result<()> {
}
// module mounted, exec modules post-fs-data scripts
if !crate::utils::is_safe_mode().unwrap_or(false) {
if !crate::utils::is_safe_mode() {
// todo: Add timeout
let _ = crate::module::exec_post_fs_data();
let _ = crate::module::load_system_prop();
@@ -149,7 +146,7 @@ pub fn on_post_data_fs() -> Result<()> {
pub fn on_services() -> Result<()> {
// exec modules service.sh scripts
if !crate::utils::is_safe_mode().unwrap_or(false) {
if !crate::utils::is_safe_mode() {
let _ = crate::module::exec_services();
} else {
println!("safe mode, skip module service scripts");

View File

@@ -1,3 +1,4 @@
#!/system/bin/sh
############################################
# KernelSU installer script
# Credit to Magisk!!!
@@ -89,14 +90,15 @@ setup_flashable() {
}
ensure_bb() {
:
}
recovery_actions() {
:
}
recovery_cleanup() {
:
}
#######################

View File

@@ -1,7 +1,7 @@
#![allow(dead_code, unused_mut, unused_variables, unused_imports)]
use anyhow::{ensure, Result};
use std::os::unix::process::CommandExt;
use anyhow::{Result, ensure};
const KERNEL_SU_OPTION: u32 = 0xDEADBEEF;
@@ -31,8 +31,7 @@ pub fn grant_root() -> Result<()> {
}
ensure!(result == KERNEL_SU_OPTION, "grant root failed");
std::process::Command::new("/system/bin/sh").exec();
Ok(())
return Err(std::process::Command::new("sh").exec().into());
}
#[cfg(not(target_os = "android"))]
@@ -56,11 +55,7 @@ pub fn get_version() -> i32 {
fn report_event(event: u64) {
#[cfg(target_os = "android")]
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_REPORT_EVENT,
event,
);
libc::prctl(KERNEL_SU_OPTION as i32, CMD_REPORT_EVENT, event);
}
}
@@ -70,4 +65,4 @@ pub fn report_post_fs_data() {
pub fn report_boot_complete() {
report_event(EVENT_BOOT_COMPLETED);
}
}

View File

@@ -1,20 +1,19 @@
use crate::{defs, restorecon};
use crate::{restorecon::setsyscon, utils::*};
use const_format::concatcp;
use java_properties::PropertiesIter;
use log::{info, warn};
use std::{
collections::HashMap,
fs::{create_dir_all, remove_dir_all, File, OpenOptions},
io::{Cursor, Write},
io::{Cursor, Read, Write},
os::unix::{prelude::PermissionsExt, process::CommandExt},
path::{Path, PathBuf},
process::{Command, Stdio},
str::FromStr,
};
use subprocess::Exec;
use zip_extensions::*;
use crate::{defs, restorecon};
use crate::{restorecon::setsyscon, utils::*};
use zip_extensions::zip_extract_file_to_memory;
use anyhow::{bail, ensure, Context, Result};
@@ -24,15 +23,15 @@ const INSTALL_MODULE_SCRIPT: &str =
fn exec_install_script(module_file: &str) -> Result<()> {
let realpath = std::fs::canonicalize(module_file)
.with_context(|| format!("realpath: {} failed", module_file))?;
.with_context(|| format!("realpath: {module_file} failed"))?;
let result = Command::new("/system/bin/sh")
let result = Command::new("sh")
.args(["-c", INSTALL_MODULE_SCRIPT])
.env("OUTFD", "1")
.env("ZIPFILE", realpath)
.stderr(Stdio::null())
.status()?;
ensure!(result.success(), "install module script failed!");
ensure!(result.success(), "Failed to install module script");
Ok(())
}
@@ -41,13 +40,8 @@ fn exec_install_script(module_file: &str) -> Result<()> {
// if someone(such as the module) install a module before the boot_completed
// then it may cause some problems, just forbid it
fn ensure_boot_completed() -> Result<()> {
// ensure getprop sys.boot_completed = 1
let output = Command::new("getprop")
.arg("sys.boot_completed")
.stdout(Stdio::piped())
.output()?;
let output = String::from_utf8_lossy(&output.stdout);
if output.trim() != "1" {
// ensure getprop sys.boot_completed == 1
if getprop("sys.boot_completed").as_deref() != Some("1") {
bail!("Android is Booting!");
}
Ok(())
@@ -103,7 +97,7 @@ fn check_image(img: &str) -> Result<()> {
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.with_context(|| format!("Failed exec e2fsck {}", img))?;
.with_context(|| format!("Failed to exec e2fsck {img}"))?;
let code = result.code();
// 0 or 1 is ok
// 0: no error
@@ -111,7 +105,7 @@ fn check_image(img: &str) -> Result<()> {
// https://man7.org/linux/man-pages/man8/e2fsck.8.html
ensure!(
code == Some(0) || code == Some(1),
"check image e2fsck exec failed: {}",
"Failed to check image, e2fsck exit code: {}",
code.unwrap_or(-1)
);
Ok(())
@@ -130,12 +124,12 @@ fn grow_image_size(img: &str, extra_size: u64) -> Result<()> {
);
let target_size = target_size / 1024 + 1;
let result = Exec::shell(format!("resize2fs {} {}K", img, target_size))
.stdout(subprocess::NullFile)
.stderr(subprocess::Redirection::Merge)
.join()
.with_context(|| format!("Failed to resize2fs {}", img))?;
ensure!(result.success(), "resize2fs exec failed.");
let result = Command::new("resize2fs")
.args([img, &format!("{target_size}K")])
.stdout(Stdio::null())
.status()
.with_context(|| format!("Failed to exec resize2fs {img}"))?;
ensure!(result.success(), "Failed to resize2fs: {}", result);
Ok(())
}
@@ -148,20 +142,33 @@ fn switch_cgroup(grp: &str, pid: u32) {
let fp = OpenOptions::new().append(true).open(path);
if let Ok(mut fp) = fp {
let _ = writeln!(fp, "{}", pid);
let _ = writeln!(fp, "{pid}");
}
}
fn switch_cgroups() -> Result<()> {
fn switch_cgroups() {
let pid = std::process::id();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if getprop("ro.config.per_app_memcg")? != "false" {
if getprop("ro.config.per_app_memcg")
.filter(|prop| prop == "false")
.is_none()
{
switch_cgroup("/dev/memcg/apps", pid);
}
}
Ok(())
fn is_executable(path: &Path) -> bool {
let mut buffer: [u8; 2] = [0; 2];
is_executable::is_executable(path)
&& File::open(path).unwrap().read_exact(&mut buffer).is_ok()
&& (
buffer == [0x23, 0x21] // shebang #!
|| buffer == [0x7f, 0x45]
// ELF magic number 0x7F 'E'
)
}
/// execute every modules' post-fs-data.sh
@@ -182,21 +189,30 @@ pub fn exec_post_fs_data() -> Result<()> {
}
println!("exec {} post-fs-data.sh", path.display());
// pre_exec is unsafe!
let mut command_new;
let mut command;
if is_executable(&post_fs_data) {
command_new = Command::new("sh");
command = command_new.arg(&post_fs_data);
} else {
command_new = Command::new(&post_fs_data);
command = &mut command_new;
};
command = command
.process_group(0)
.current_dir(path)
.env("KSU", "true");
unsafe {
Command::new("/system/bin/sh")
.arg(&post_fs_data)
.process_group(0)
.pre_exec(|| {
// ignore the error?
let _ = switch_cgroups();
Ok(())
})
.current_dir(path)
.env("KSU", "true")
.status()
.with_context(|| format!("Failed to exec {}", post_fs_data.display()))?;
command = command.pre_exec(|| {
// ignore the error?
switch_cgroups();
Ok(())
});
}
command
.status()
.with_context(|| format!("Failed to exec {}", post_fs_data.display()))?;
}
Ok(())
@@ -220,21 +236,29 @@ pub fn exec_services() -> Result<()> {
}
println!("exec {} service.sh", path.display());
// pre_exec is unsafe!
let mut command_new;
let mut command;
if is_executable(&service) {
command_new = Command::new("sh");
command = command_new.arg(&service);
} else {
command_new = Command::new(&service);
command = &mut command_new;
};
command = command
.process_group(0)
.current_dir(path)
.env("KSU", "true");
unsafe {
Command::new("/system/bin/sh")
.arg(&service)
.process_group(0)
.pre_exec(|| {
// ignore the error?
let _ = switch_cgroups();
Ok(())
})
.current_dir(path)
.env("KSU", "true")
.spawn() // don't wait
.with_context(|| format!("Failed to exec {}", service.display()))?;
command = command.pre_exec(|| {
// ignore the error?
switch_cgroups();
Ok(())
});
}
command
.spawn() // don't wait
.with_context(|| format!("Failed to exec {}", service.display()))?;
}
Ok(())
@@ -271,7 +295,7 @@ pub fn load_system_prop() -> Result<()> {
}
println!("load {} system.prop", path.display());
// resetprop --file system.prop
// resetprop -n --file system.prop
Command::new(RESETPROP_PATH)
.arg("-n")
.arg("--file")
@@ -335,7 +359,6 @@ pub fn install_module(zip: String) -> Result<()> {
let default_reserve_size = 64 * 1024 * 1024;
let zip_uncompressed_size = get_zip_uncompressed_size(&zip)?;
let grow_size = default_reserve_size + zip_uncompressed_size;
let grow_size_per_m = grow_size / 1024 / 1024 + 1;
println!("- Preparing image");
println!(
@@ -346,21 +369,21 @@ pub fn install_module(zip: String) -> Result<()> {
if !modules_img_exist && !modules_update_img_exist {
// if no modules and modules_update, it is brand new installation, we should create a new img
// create a tmp module img and mount it to modules_update
let result = Exec::shell(format!(
"dd if=/dev/zero of={} bs=1M count={}",
tmp_module_img, grow_size_per_m
))
.stdout(subprocess::NullFile)
.stderr(subprocess::Redirection::Merge)
.join()?;
ensure!(result.success(), "create ext4 image failed!");
File::create(tmp_module_img)
.context("Failed to create ext4 image file")?
.set_len(grow_size)
.context("Failed to extend ext4 image")?;
// format the img to ext4 filesystem
let result = Exec::shell(format!("mkfs.ext4 {}", tmp_module_img))
.stdout(subprocess::NullFile)
.stderr(subprocess::Redirection::Merge)
.join()?;
ensure!(result.success(), "format ext4 image failed!");
let result = Command::new("mkfs.ext4")
.arg(tmp_module_img)
.stdout(Stdio::null())
.output()?;
ensure!(
result.status.success(),
"Failed to format ext4 image: {}",
String::from_utf8(result.stderr).unwrap()
);
check_image(tmp_module_img)?;
} else if modules_update_img_exist {

View File

@@ -1,16 +1,13 @@
use anyhow::ensure;
use anyhow::Ok;
use anyhow::Result;
use subprocess::Exec;
use anyhow::{Context, Ok, Result};
use extattr::{setxattr, Flags as XattrFlags};
use jwalk::{Parallelism::Serial, WalkDir};
const SYSTEM_CON: &str = "u:object_r:system_file:s0";
const _ADB_CON: &str = "u:object_r:adb_data_file:s0";
pub fn setcon(path: &str, con: &str) -> Result<()> {
// todo use libselinux directly
let cmd = format!("chcon {} {}", con, path);
let result = Exec::shell(cmd).join()?;
ensure!(result.success(), "chcon for: {} failed.", path);
setxattr(path, "security.selinux", con, XattrFlags::empty())
.with_context(|| format!("Failed to change SELinux context for {path}"))?;
Ok(())
}
@@ -19,9 +16,17 @@ pub fn setsyscon(path: &str) -> Result<()> {
}
pub fn restore_syscon(dir: &str) -> Result<()> {
// todo use libselinux directly
let cmd = format!("chcon -R {} {}", SYSTEM_CON, dir);
let result = Exec::shell(cmd).join()?;
ensure!(result.success(), "chcon for: {} failed.", dir);
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path()) {
setxattr(&path, "security.selinux", SYSTEM_CON, XattrFlags::empty()).with_context(
|| {
format!(
"Failed to change SELinux context for {}",
path.to_str().unwrap()
)
},
)?;
}
}
Ok(())
}

View File

@@ -1,18 +1,14 @@
use std::{
path::Path,
process::{Command, Stdio},
};
use std::path::Path;
use anyhow::{ensure, Result};
use anyhow::{ensure, Context, Result};
use retry::delay::NoDelay;
use subprocess::Exec;
use sys_mount::{unmount, FilesystemType, Mount, UnmountFlags};
fn do_mount_image(src: &str, target: &str) -> Result<()> {
let result = Exec::shell(format!("mount -t ext4 {} {}", src, target))
.stdout(subprocess::NullFile)
.stderr(subprocess::Redirection::Merge)
.join()?;
ensure!(result.success(), "do mount: {} -> {} failed.", src, target);
Mount::builder()
.fstype(FilesystemType::from("ext4"))
.mount(src, target)
.with_context(|| format!("Failed to do mount: {src} -> {target}"))?;
Ok(())
}
@@ -20,16 +16,12 @@ pub fn mount_image(src: &str, target: &str) -> Result<()> {
// umount target first.
let _ = umount_dir(target);
let result = retry::retry(NoDelay.take(3), || do_mount_image(src, target));
ensure!(result.is_ok(), "mount: {} -> {} failed.", src, target);
ensure!(result.is_ok(), "Failed to mount {} -> {}", src, target);
Ok(())
}
pub fn umount_dir(src: &str) -> Result<()> {
let result = Exec::shell(format!("umount {}", src))
.stdout(subprocess::NullFile)
.stderr(subprocess::Redirection::Merge)
.join()?;
ensure!(result.success(), "umount: {} failed", src);
unmount(src, UnmountFlags::empty()).with_context(|| format!("Failed to umount {src}"))?;
Ok(())
}
@@ -41,17 +33,17 @@ pub fn ensure_clean_dir(dir: &str) -> Result<()> {
Ok(std::fs::create_dir_all(path)?)
}
pub fn getprop(prop: &str) -> Result<String> {
let output = Command::new("getprop")
.arg(prop)
.stdout(Stdio::piped())
.output()?;
let output = String::from_utf8_lossy(&output.stdout);
Ok(output.trim().to_string())
pub fn getprop(prop: &str) -> Option<String> {
android_properties::getprop(prop).value()
}
pub fn is_safe_mode() -> Result<bool> {
Ok(getprop("persist.sys.safemode")?.eq("1") || getprop("ro.sys.safemode")?.eq("1"))
pub fn is_safe_mode() -> bool {
getprop("persist.sys.safemode")
.filter(|prop| prop == "1")
.is_some()
|| getprop("ro.sys.safemode")
.filter(|prop| prop == "1")
.is_some()
}
pub fn get_zip_uncompressed_size(zip_path: &str) -> Result<u64> {