Build KernelSU as LKM (#1254)

Co-authored-by: weishu <twsxtd@gmail.com>
This commit is contained in:
Ylarod
2024-03-15 18:53:24 +08:00
committed by GitHub
parent e3998c0744
commit 7568d55be1
27 changed files with 1091 additions and 202 deletions

Binary file not shown.

Binary file not shown.

BIN
userspace/ksud/bin/x86_64/ksuinit Executable file

Binary file not shown.

View File

@@ -1,29 +1,38 @@
use anyhow::Result;
use const_format::concatcp;
use rust_embed::RustEmbed;
use std::path::Path;
use crate::{defs::BINARY_DIR, utils};
pub const RESETPROP_PATH: &str = concatcp!(BINARY_DIR, "resetprop");
pub const BUSYBOX_PATH: &str = concatcp!(BINARY_DIR, "busybox");
pub const BOOTCTL_PATH: &str = concatcp!(BINARY_DIR, "bootctl");
#[cfg(target_arch = "aarch64")]
#[derive(RustEmbed)]
#[folder = "bin/aarch64"]
struct Asset;
#[cfg(target_arch = "x86_64")]
#[cfg(all(target_arch = "x86_64", target_os = "android"))]
#[derive(RustEmbed)]
#[folder = "bin/x86_64"]
struct Asset;
// IF NOT x86_64 ANDROID, ie. macos, linux, windows, always use aarch64
#[cfg(not(all(target_arch = "x86_64", target_os = "android")))]
#[derive(RustEmbed)]
#[folder = "bin/aarch64"]
struct Asset;
pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> {
for file in Asset::iter() {
utils::ensure_binary(
format!("{BINARY_DIR}{file}"),
&Asset::get(&file).unwrap().data,
ignore_if_exist,
)?
if file == "ksuinit" {
continue;
}
let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?;
utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)?
}
Ok(())
}
pub fn copy_assets_to_file(name: &str, dst: impl AsRef<Path>) -> Result<()> {
let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?;
std::fs::write(dst, asset.data)?;
Ok(())
}

View File

@@ -1,27 +1,52 @@
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use is_executable::IsExecutable;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::process::Stdio;
use which::which;
use crate::utils;
use crate::{assets, utils};
#[cfg(unix)]
#[cfg(target_os = "android")]
fn ensure_gki_kernel() -> Result<()> {
let version =
procfs::sys::kernel::Version::current().with_context(|| "get kernel version failed")?;
let is_gki = version.major == 5 && version.minor >= 10 || version.major > 5;
let version = get_kernel_version()?;
let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5;
ensure!(is_gki, "only support GKI kernel");
Ok(())
}
#[cfg(target_os = "android")]
pub fn get_kernel_version() -> Result<(i32, i32, i32)> {
use regex::Regex;
let uname = rustix::system::uname();
let version = uname.release().to_string_lossy();
let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?;
if let Some(captures) = re.captures(&version) {
let major = captures
.get(1)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Major version parse error"))?;
let minor = captures
.get(2)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Minor version parse error"))?;
let patch = captures
.get(3)
.and_then(|m| m.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Patch version parse error"))?;
Ok((major, minor, patch))
} else {
Err(anyhow!("Invalid kernel version string"))
}
}
fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> {
let status = Command::new(magiskboot)
.current_dir(workding_dir)
@@ -63,8 +88,28 @@ pub fn patch(
out: Option<PathBuf>,
magiskboot_path: Option<PathBuf>,
) -> Result<()> {
let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot_path);
if let Err(ref e) = result {
println!("- Install Error: {e}");
}
result
}
#[allow(clippy::too_many_arguments)]
fn do_patch(
image: Option<PathBuf>,
kernel: Option<PathBuf>,
kmod: Option<PathBuf>,
init: Option<PathBuf>,
ota: bool,
flash: bool,
out: Option<PathBuf>,
magiskboot_path: Option<PathBuf>,
) -> Result<()> {
println!(include_str!("banner"));
if image.is_none() {
#[cfg(unix)]
#[cfg(target_os = "android")]
ensure_gki_kernel()?;
}
@@ -76,19 +121,17 @@ pub fn patch(
"init and module must not be specified."
);
} else {
ensure!(
init.is_some() && kmod.is_some(),
"init and module must be specified"
);
ensure!(kmod.is_some(), "module must be specified");
}
let workding_dir = tempdir::TempDir::new("KernelSU")?;
let workding_dir =
tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?;
let bootimage;
let mut bootdevice = None;
if let Some(image) = image {
if let Some(ref image) = image {
ensure!(image.exists(), "boot image not found");
bootimage = std::fs::canonicalize(image)?;
} else {
@@ -111,7 +154,7 @@ pub fn patch(
format!("/dev/block/by-name/boot{slot_suffix}")
};
println!("bootdevice: {boot_partition}");
println!("- Bootdevice: {boot_partition}");
let tmp_boot_path = workding_dir.path().join("boot.img");
dd(&boot_partition, &tmp_boot_path)?;
@@ -122,37 +165,56 @@ pub fn patch(
bootdevice = Some(boot_partition);
};
println!("boot image: {bootimage:?}");
// try extract magiskboot/bootctl
let _ = assets::ensure_binaries(false);
let magiskboot = magiskboot_path
.map(std::fs::canonicalize)
.transpose()?
.unwrap_or_else(|| "magiskboot".into());
if !magiskboot.is_executable() {
#[cfg(unix)]
std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755))
.with_context(|| "set magiskboot executable failed".to_string())?;
}
ensure!(magiskboot.exists(), "magiskboot not found");
// extract magiskboot
let magiskboot = {
if which("magiskboot").is_ok() {
let _ = assets::ensure_binaries(true);
"magiskboot".into()
} else {
// magiskboot is not in $PATH, use builtin or specified one
let magiskboot = if let Some(magiskboot_path) = magiskboot_path {
std::fs::canonicalize(magiskboot_path)?
} else {
let magiskboot_path = workding_dir.path().join("magiskboot");
assets::copy_assets_to_file("magiskboot", &magiskboot_path)
.with_context(|| "copy magiskboot failed")?;
magiskboot_path
};
ensure!(magiskboot.exists(), "{magiskboot:?} is not exist");
#[cfg(unix)]
let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755));
magiskboot
}
};
if let Some(kernel) = kernel {
std::fs::copy(kernel, workding_dir.path().join("kernel"))
.with_context(|| "copy kernel from failed".to_string())?;
}
if let (Some(kmod), Some(init)) = (kmod, init) {
if let Some(kmod) = kmod {
println!("- Preparing assets");
std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko"))
.with_context(|| "copy kernel module failed".to_string())?;
std::fs::copy(init, workding_dir.path().join("init"))
.with_context(|| "copy init failed".to_string())?;
let init_file = workding_dir.path().join("init");
if let Some(init) = init {
std::fs::copy(init, workding_dir.path().join("init"))
.with_context(|| "copy init failed".to_string())?;
} else {
crate::assets::copy_assets_to_file("ksuinit", init_file)
.with_context(|| "copy ksuinit failed")?;
}
// magiskboot unpack boot.img
// magiskboot cpio ramdisk.cpio 'cp init init.real'
// magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init'
// magiskboot cpio ramdisk.cpio 'add 0755 <kmod> kernelsu.ko'
println!("- Unpacking boot image");
let status = Command::new(&magiskboot)
.current_dir(workding_dir.path())
.stdout(Stdio::null())
@@ -162,6 +224,10 @@ pub fn patch(
.status()?;
ensure!(status.success(), "magiskboot unpack failed");
let not_magisk = do_cpio_cmd(&magiskboot, workding_dir.path(), "test").is_ok();
ensure!(not_magisk, "Cannot work with Magisk patched image");
println!("- Adding KernelSU LKM");
let is_kernelsu_patched =
do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok();
if !is_kernelsu_patched {
@@ -180,6 +246,7 @@ pub fn patch(
)?;
}
println!("- Repacking boot image");
// magiskboot repack boot.img
let status = Command::new(&magiskboot)
.current_dir(workding_dir.path())
@@ -189,18 +256,25 @@ pub fn patch(
.arg(bootimage.display().to_string())
.status()?;
ensure!(status.success(), "magiskboot repack failed");
let new_boot = workding_dir.path().join("new-boot.img");
let out = out.unwrap_or(std::env::current_dir()?);
if image.is_some() {
// if image is specified, write to output file
let output_dir = out.unwrap_or(std::env::current_dir()?);
let now = chrono::Utc::now();
let output_image =
output_dir.join(format!("kernelsu_boot_{}.img", now.format("%Y%m%d_%H%M%S")));
let now = chrono::Utc::now();
let output_image = out.join(format!(
"kernelsu_patched_boot_{}.img",
now.format("%Y%m%d_%H%M%S")
));
std::fs::copy(workding_dir.path().join("new-boot.img"), &output_image)
.with_context(|| "copy out new boot failed".to_string())?;
if std::fs::rename(&new_boot, &output_image).is_err() {
std::fs::copy(&new_boot, &output_image)
.with_context(|| "copy out new boot failed".to_string())?;
}
println!("- Output file is written to");
println!("- {}", output_image.display().to_string().trim_matches('"'));
}
if flash {
println!("- Flashing new boot image");
let Some(bootdevice) = bootdevice else {
bail!("boot device not found")
};
@@ -210,7 +284,52 @@ pub fn patch(
.status()?;
ensure!(status.success(), "set boot device rw failed");
dd(&output_image, &bootdevice).with_context(|| "flash boot failed")?;
dd(&new_boot, &bootdevice).with_context(|| "flash boot failed")?;
if ota {
post_ota()?;
}
}
println!("- Done!");
Ok(())
}
fn post_ota() -> Result<()> {
use crate::defs::ADB_DIR;
use assets::BOOTCTL_PATH;
let status = Command::new(BOOTCTL_PATH).arg("hal-info").status()?;
if !status.success() {
return Ok(());
}
let current_slot = Command::new(BOOTCTL_PATH)
.arg("get-current-slot")
.output()?
.stdout;
let current_slot = String::from_utf8(current_slot)?;
let current_slot = current_slot.trim();
let target_slot = if current_slot == "0" { 1 } else { 0 };
Command::new(BOOTCTL_PATH)
.arg(format!("set-active-boot-slot {target_slot}"))
.status()?;
let post_ota_sh = std::path::Path::new(ADB_DIR)
.join("post-fs-data.d")
.join("post_ota.sh");
let sh_content = format!(
r###"
{BOOTCTL_PATH} mark-boot-successful
rm -f {BOOTCTL_PATH}
rm -f /data/adb/post-fs-data.d/post_ota.sh
"###
);
std::fs::write(&post_ota_sh, sh_content)?;
#[cfg(unix)]
std::fs::set_permissions(post_ota_sh, std::fs::Permissions::from_mode(0o755))?;
Ok(())
}

View File

@@ -7,7 +7,7 @@ use android_logger::Config;
#[cfg(target_os = "android")]
use log::LevelFilter;
use crate::{apk_sign, debug, defs, init_event, ksucalls, module, utils};
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
/// KernelSU userspace cli
#[derive(Parser, Debug)]
@@ -60,10 +60,10 @@ enum Commands {
kernel: Option<PathBuf>,
/// LKM module path to replace
#[arg(short, long, requires("init"))]
#[arg(short, long)]
module: Option<PathBuf>,
/// init to be replaced, if use LKM, this must be specified
/// init to be replaced
#[arg(short, long, requires("module"))]
init: Option<PathBuf>,
@@ -304,7 +304,7 @@ pub fn run() -> Result<()> {
utils::copy_sparse_file(src, dst, punch_hole)?;
Ok(())
}
Debug::Test => todo!(),
Debug::Test => assets::ensure_binaries(false),
},
Commands::BootPatch {

View File

@@ -79,12 +79,12 @@ fn set_identity(uid: u32, gid: u32, groups: &[u32]) {
}
}
#[cfg(not(unix))]
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn root_shell() -> Result<()> {
unimplemented!()
}
#[cfg(unix)]
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn root_shell() -> Result<()> {
// we are root now, this was set in kernel!