diff --git a/.github/workflows/build-manager.yml b/.github/workflows/build-manager.yml index c6c78cca9..50b59281e 100644 --- a/.github/workflows/build-manager.yml +++ b/.github/workflows/build-manager.yml @@ -3,7 +3,6 @@ name: Build Manager on: workflow_dispatch: push: - branches: [ "main", "dev", "ci", "sync-upstream" ] paths: - '.github/workflows/build-manager.yml' - '.github/workflows/build-lkm.yml' diff --git a/kernel/build-all.sh b/kernel/build-all.sh index 8c48f846b..72d8ee2b9 100755 --- a/kernel/build-all.sh +++ b/kernel/build-all.sh @@ -7,8 +7,6 @@ else KMIS=$1 fi -cd "$(dirname "$(readlink -f "$0")")/.." - # Some patch is required to use separate build dir when building for android16-6.12, see: # https://github.com/5ec1cff/ddk#local-%E6%A8%A1%E5%BC%8F%E6%9E%84%E5%BB%BA%E9%80%82%E7%94%A8%E4%BA%8E%E5%A4%9A%E4%B8%AA-target-%E7%89%88%E6%9C%AC%E7%9A%84%E5%86%85%E6%A0%B8%E6%A8%A1%E5%9D%97 @@ -17,10 +15,10 @@ mv .ddk-version .ddk-version.bak 2> /dev/null || true for kmi in $KMIS; do echo "========== Building $kmi ==========" ODIR="$(realpath .)/out/$kmi" - if ddk build "$kmi" "ODIR=$ODIR" -e CONFIG_KSU=m -e CONFIG_KSU_TRACEPOINT_HOOK=y -e CONFIG_KSU_MULTI_MANAGER_SUPPORT=y -- -C kernel; then + if ddk build "$kmi" "ODIR=$ODIR" -e CONFIG_KSU=m -e CONFIG_KSU_TRACEPOINT_HOOK=y -e CONFIG_KSU_MULTI_MANAGER_SUPPORT=y; then if [ -f "$ODIR/kernelsu.ko" ]; then - cp "$ODIR/kernelsu.ko" "kernel/kernelsu-${kmi}.ko" - llvm-strip -d "kernel/kernelsu-${kmi}.ko" + cp "$ODIR/kernelsu.ko" "kernelsu-${kmi}.ko" + llvm-strip -d "kernelsu-${kmi}.ko" echo "✓ Built kernelsu-${kmi}.ko" fi else @@ -32,5 +30,4 @@ done mv .ddk-version.bak .ddk-version 2> /dev/null || true echo "========== Final output ==========" -cd kernel ls -l kernelsu-*.ko diff --git a/manager/app/src/main/java/com/resukisu/resukisu/ui/theme/Theme.kt b/manager/app/src/main/java/com/resukisu/resukisu/ui/theme/Theme.kt index 2d591b445..f91d1e0a6 100644 --- a/manager/app/src/main/java/com/resukisu/resukisu/ui/theme/Theme.kt +++ b/manager/app/src/main/java/com/resukisu/resukisu/ui/theme/Theme.kt @@ -1355,12 +1355,6 @@ private fun createColorScheme( isDark = darkTheme, style = ThemeConfig.dynamicPaletteStyle, specVersion = ThemeConfig.dynamicColorSpec, - modifyColorScheme = { scheme -> - scheme.copy( - background = if (CardConfig.isCustomBackgroundEnabled) Color.Transparent else scheme.background, - surface = if (CardConfig.isCustomBackgroundEnabled) Color.Transparent else scheme.surface, - ) - } ) } diff --git a/manager/app/src/main/res/values-uk/strings.xml b/manager/app/src/main/res/values-uk/strings.xml index cee485137..864255b50 100644 --- a/manager/app/src/main/res/values-uk/strings.xml +++ b/manager/app/src/main/res/values-uk/strings.xml @@ -5,11 +5,15 @@ Не встановлено Натисніть, щоб встановити Працює + Інформація про версію + Інформація про стан + Суперкористувач: %1$d, Модулі: %2$d Не підтримується Драйвер KernelSU не виявлено у вашому ядрі. Можливо, у вас неправильне ядро. Версія ядра Версія SuSFS Версія менеджера + Версія драйвера ядра Статус SELinux Вимкнено Примусовий @@ -42,6 +46,7 @@ Не вдалося ввімкнути модуль: %s Не вдалося вимкнути модуль: %s Немає встановлених модулів + Жодного збігу не знайдено Модулі Репозиторій модулів Сортувати (Спочатку дії) @@ -124,6 +129,11 @@ Початок завантаження: %s Дію модуля виконано успішно. Доступна нова версія %s, натисніть для оновлення. + Доступна нова бета-версія %1$d. Натисніть, щоб оновити + Не вдалося перевірити бета-оновлення. Потягніть, щоб оновити, і спробуйте ще раз. + Стабільне оновлення + Бета-оновлення + Версія: %1$s (%2$d)\nАрхітектура: %3$s Запустити Примусово зупинити Перезапустити @@ -156,6 +166,8 @@ Не вдалося завантажити список змін: %s Сповіщення про оновлення Автоматично перевіряти наявність оновлень модуля та менеджера + Перевірити бета-оновлення + Автоматично перевіряти бета-збірки з головної гілки Не вдалося надати root-права! Це PR debug-збірка. НЕ використовуйте її в робочому середовищі! Дія diff --git a/manager/app/src/main/res/values-zh-rCN/strings.xml b/manager/app/src/main/res/values-zh-rCN/strings.xml index a1132a0a8..f6b42589a 100644 --- a/manager/app/src/main/res/values-zh-rCN/strings.xml +++ b/manager/app/src/main/res/values-zh-rCN/strings.xml @@ -12,8 +12,8 @@ 内核上未检测到 KernelSU 驱动程序,内核错误? 内核版本 SuSFS 版本 - 内核驱动版本 管理器版本 + 内核驱动版本 SELinux 状态 被禁用 强制执行 diff --git a/userspace/ksud/src/boot_patch.rs b/userspace/ksud/src/boot_patch.rs index b603e8b39..d0b1904e3 100644 --- a/userspace/ksud/src/boot_patch.rs +++ b/userspace/ksud/src/boot_patch.rs @@ -2,7 +2,7 @@ use std::os::unix::fs::PermissionsExt; use std::{ fs::File, - io::{BufReader, Cursor, Read, Seek, SeekFrom}, + io::{Cursor, Seek, SeekFrom}, path::PathBuf, }; @@ -293,14 +293,29 @@ rm -f /data/adb/post-fs-data.d/post_ota.sh #[cfg(target_os = "android")] pub use android::*; +fn map_file(file: &PathBuf) -> Result { + let mut f = File::open(file).with_context(|| format!("open {}", file.display()))?; + let len = f + .seek(SeekFrom::End(0)) + .with_context(|| format!("seek end of {}", file.display()))? as usize; + let mmap = unsafe { MmapOptions::new().len(len).map(&f)? }; + Ok(mmap) +} + #[allow(clippy::needless_pass_by_value)] fn parse_kmi(buffer: Vec) -> Result { let re = Regex::new(r"(\d+\.\d+)(?:\S+)?(android\d+)").context("Failed to compile regex")?; buffer - .windows(3) + .windows(4) .enumerate() .filter(|(_, x)| { - x[1] == b'.' && (x[0] == b'5' || x[0] == b'6') && (x[2] >= b'0' && x[2] <= b'9') + x[1] == b'.' + && x[2].is_ascii_digit() + && match x[0] { + b'5' => x[3].is_ascii_digit(), + b'6'..=b'9' => true, + _ => false, + } }) .find_map(|(i, _)| { let a = &buffer[i..buffer.len().min(i + 100)]; @@ -325,19 +340,13 @@ fn parse_kmi(buffer: Vec) -> Result { } fn parse_kmi_from_kernel(kernel: &PathBuf) -> Result { - let file = File::open(kernel).context("Failed to open kernel file")?; - let mut reader = BufReader::new(file); - let mut buffer = Vec::new(); - reader - .read_to_end(&mut buffer) - .context("Failed to read kernel file")?; - - parse_kmi(buffer) + let data = std::fs::read(kernel).context("Failed to read kernel file")?; + + parse_kmi(data) } fn parse_kmi_from_boot(image: &PathBuf) -> Result { - let image = unsafe { Mmap::map(&File::open(image)?)? }; - + let image = map_file(image)?; let bootimage = BootImage::parse(&image)?; if let Some(kernel) = bootimage.get_blocks().get_kernel() { let mut output = Vec::::new(); @@ -505,11 +514,9 @@ pub fn patch(args: BootPatchArgs) -> Result<()> { if ota { let slot_suffix = get_slot_suffix(true); println!("- Trying to auto detect KMI version from boot"); - if let Ok(kmi) = parse_kmi_from_boot(&PathBuf::from(&format!( + return parse_kmi_from_boot(&PathBuf::from(&format!( "/dev/block/by-name/boot{slot_suffix}" - ))) { - return Ok(kmi); - } + ))); } #[cfg(target_os = "android")] match get_current_kmi() { @@ -912,11 +919,3 @@ fn rebuild_without_ksu( patcher.patch(&mut buf)?; Ok(buf.into_inner()) } -fn map_file(file: &PathBuf) -> Result { - unsafe { - let mut file = File::open(file)?; - Ok(MmapOptions::new() - .len(file.seek(SeekFrom::End(0))? as usize) - .map(&file)?) - } -} diff --git a/userspace/ksuinit/src/lib.rs b/userspace/ksuinit/src/lib.rs index 058db3382..1a62d4c2b 100644 --- a/userspace/ksuinit/src/lib.rs +++ b/userspace/ksuinit/src/lib.rs @@ -1,11 +1,12 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Result, bail}; use goblin::elf::{Elf, section_header, sym::Sym}; use rustix::system::init_module; use scroll::{Pwrite, ctx::SizeWith}; use std::collections::HashMap; use std::ffi::CStr; -use std::fs::{self, File}; -use std::io::{BufRead, BufReader}; +use std::fs::{self, File, OpenOptions}; +use std::io::{BufRead, BufReader, ErrorKind, Read, Seek, SeekFrom}; +use std::os::unix::fs::OpenOptionsExt; struct Kptr { value: String, @@ -82,6 +83,235 @@ pub fn for_each_kernel_symbols Result>(mut f: Ok(()) } +const O_NONBLOCK: i32 = 0x800; + +fn open_kmsg_at_end() -> Result { + let mut last_error = None; + + for path in ["/dev/kmsg", "/kmsg"] { + match OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open(path) + { + Ok(mut file) => { + file.seek(SeekFrom::End(0)) + .with_context(|| format!("Cannot seek {path} to end"))?; + + log::info!("Reading kernel log from {path}"); + return Ok(file); + } + Err(error) => { + last_error = Some((path, error)); + } + } + } + + match last_error { + Some((path, error)) => { + Err(error).with_context(|| format!("Cannot open kernel log device, last tried {path}")) + } + None => bail!("No kernel log device candidate"), + } +} + +fn read_new_kmsg(file: &mut File) -> Result { + let mut output = Vec::new(); + let mut record = [0u8; 8192]; + + loop { + match file.read(&mut record) { + Ok(0) => break, + Ok(length) => { + output.extend_from_slice(&record[..length]); + output.push(b'\n'); + } + Err(error) if error.kind() == ErrorKind::WouldBlock => break, + Err(error) => return Err(error).context("Cannot read /dev/kmsg"), + } + } + + Ok(String::from_utf8_lossy(&output).into_owned()) +} + +fn extract_required_vermagic(kmsg: &str) -> Option { + const PREFIX: &str = "version magic '"; + const SEPARATOR: &str = "' should be '"; + + for record in kmsg.lines().rev() { + let message = record + .split_once(';') + .map(|(_, message)| message) + .unwrap_or(record); + + let Some(prefix_position) = message.find(PREFIX) else { + continue; + }; + let after_prefix = &message[prefix_position + PREFIX.len()..]; + + let Some(separator_position) = after_prefix.find(SEPARATOR) else { + continue; + }; + let required = &after_prefix[separator_position + SEPARATOR.len()..]; + + let Some(end_quote) = required.find('\'') else { + continue; + }; + let required = &required[..end_quote]; + + if !required.is_empty() { + return Some(required.to_owned()); + } + } + + None +} + +fn align_up(value: usize, alignment: usize) -> Result { + let alignment = alignment.max(1); + + if !alignment.is_power_of_two() { + bail!("Invalid ELF alignment: {alignment}"); + } + + value + .checked_add(alignment - 1) + .map(|value| value & !(alignment - 1)) + .context("ELF alignment overflow") +} + +fn write_elf64_word( + buffer: &mut [u8], + offset: usize, + value: u64, + little_endian: bool, +) -> Result<()> { + let end = offset.checked_add(8).context("ELF write overflow")?; + let destination = buffer + .get_mut(offset..end) + .context("ELF write outside module buffer")?; + + let bytes = if little_endian { + value.to_le_bytes() + } else { + value.to_be_bytes() + }; + destination.copy_from_slice(&bytes); + Ok(()) +} + +fn replace_module_vermagic(buffer: &mut Vec, required_vermagic: &str) -> Result<()> { + struct ModinfoLocation { + offset: usize, + size: usize, + section_header_offset: usize, + alignment: usize, + little_endian: bool, + } + + let location = { + let elf = Elf::parse(buffer)?; + + if !elf.is_64 { + bail!("Only ELF64 modules are supported"); + } + + let section_table_offset = + usize::try_from(elf.header.e_shoff).context("Section table offset overflow")?; + let section_entry_size = usize::from(elf.header.e_shentsize); + let mut location = None; + + for (index, section) in elf.section_headers.iter().enumerate() { + let Some(name) = elf.shdr_strtab.get_at(section.sh_name) else { + continue; + }; + if name != ".modinfo" { + continue; + } + + let offset = usize::try_from(section.sh_offset).context(".modinfo offset overflow")?; + let size = usize::try_from(section.sh_size).context(".modinfo size overflow")?; + let end = offset + .checked_add(size) + .context(".modinfo range overflow")?; + + if end > buffer.len() { + bail!(".modinfo is outside module buffer"); + } + + let section_header_offset = section_table_offset + .checked_add( + index + .checked_mul(section_entry_size) + .context("Section index overflow")?, + ) + .context("Section header offset overflow")?; + + location = Some(ModinfoLocation { + offset, + size, + section_header_offset, + alignment: usize::try_from(section.sh_addralign).unwrap_or(1).max(1), + little_endian: elf.little_endian, + }); + break; + } + + location.context("Module has no .modinfo section")? + }; + + let old_modinfo = &buffer[location.offset..location.offset + location.size]; + let replacement = format!("vermagic={required_vermagic}"); + let mut new_modinfo = Vec::with_capacity(old_modinfo.len().max(replacement.len() + 1)); + let mut replaced = false; + + for entry in old_modinfo.split(|byte| *byte == 0) { + if entry.is_empty() { + continue; + } + + if entry.starts_with(b"vermagic=") { + if !replaced { + new_modinfo.extend_from_slice(replacement.as_bytes()); + new_modinfo.push(0); + replaced = true; + } + } else { + new_modinfo.extend_from_slice(entry); + new_modinfo.push(0); + } + } + + if !replaced { + new_modinfo.extend_from_slice(replacement.as_bytes()); + new_modinfo.push(0); + } + + let new_offset = align_up(buffer.len(), location.alignment)?; + buffer.resize(new_offset, 0); + buffer.extend_from_slice(&new_modinfo); + + // Elf64_Shdr: sh_offset at +0x18, sh_size at +0x20. + write_elf64_word( + buffer, + location.section_header_offset + 0x18, + new_offset as u64, + location.little_endian, + )?; + write_elf64_word( + buffer, + location.section_header_offset + 0x20, + new_modinfo.len() as u64, + location.little_endian, + )?; + + log::warn!( + "Replaced module vermagic with kernel-required value: {:?}", + required_vermagic + ); + Ok(()) +} + /// Relocate undefined symbols in an ELF kernel module buffer using /proc/kallsyms, /// then load it via init_module syscall. pub fn load_module(data: &[u8], params: &CStr) -> Result<()> { @@ -124,8 +354,39 @@ pub fn load_module(data: &[u8], params: &CStr) -> Result<()> { log::warn!("Cannot find symbol: {}", name); } - init_module(&buffer, params).context("init_module failed.")?; - Ok(()) + let mut kmsg = match open_kmsg_at_end() { + Ok(file) => Some(file), + Err(error) => { + log::warn!("Cannot prepare kmsg fallback: {error:#}"); + None + } + }; + + match init_module(&buffer, params) { + Ok(()) => Ok(()), + Err(first_error) => { + let logs = match kmsg.as_mut() { + Some(file) => read_new_kmsg(file).unwrap_or_default(), + None => String::new(), + }; + + let Some(required_vermagic) = extract_required_vermagic(&logs) else { + log::error!("Kernel module loading log:\n{}", logs); + return Err(first_error).context("init_module failed without vermagic mismatch"); + }; + + log::warn!( + "Kernel requires vermagic {:?}; replacing and retrying", + required_vermagic + ); + + replace_module_vermagic(&mut buffer, &required_vermagic) + .context("Cannot replace module vermagic")?; + + init_module(&buffer, params).context("init_module failed after replacing vermagic")?; + Ok(()) + } + } } fn has_kernelsu_legacy() -> bool {