From c61e643aee4f1321583c98b00931f6eb90687819 Mon Sep 17 00:00:00 2001 From: ReSukiSU bot Date: Thu, 16 Jul 2026 13:35:02 +0800 Subject: [PATCH 1/5] manager: sync translation from Crowdin (#281) Co-authored-by: Crowdin Bot --- manager/app/src/main/res/values-uk/strings.xml | 12 ++++++++++++ manager/app/src/main/res/values-zh-rCN/strings.xml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) 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 状态 被禁用 强制执行 From 227ce54f1f6deca7043654f3acfa4102a9fab8c2 Mon Sep 17 00:00:00 2001 From: Tools-cx-app Date: Thu, 16 Jul 2026 16:02:54 +0800 Subject: [PATCH 2/5] ksud: fix mmap file len is empty reverts error impl [220c915f0c81535cac55b5d26ff10d3ea46f4f7e]. --- userspace/ksud/src/boot_patch.rs | 47 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/userspace/ksud/src/boot_patch.rs b/userspace/ksud/src/boot_patch.rs index b603e8b39..8744b81f4 100644 --- a/userspace/ksud/src/boot_patch.rs +++ b/userspace/ksud/src/boot_patch.rs @@ -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)?) - } -} From aa32736680c91705ef68ab634cf276a7c42612d4 Mon Sep 17 00:00:00 2001 From: Tools-cx-app Date: Thu, 16 Jul 2026 16:06:36 +0800 Subject: [PATCH 3/5] ksud: remove unused import --- userspace/ksud/src/boot_patch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userspace/ksud/src/boot_patch.rs b/userspace/ksud/src/boot_patch.rs index 8744b81f4..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, }; From 83d1806eda619f68d3af4b69600b925b697ef410 Mon Sep 17 00:00:00 2001 From: AlexLiuDev233 Date: Sat, 18 Jul 2026 18:52:24 +0800 Subject: [PATCH 4/5] ksuinit: compat for version magic mismatch kernel Some device, e.g. iQOO Neo9 Pro Their kernel added "vivo" to thier modversions Let's read kmsg to get current version magic, replace ourselves, and attempt load again This solved some VIVO devices can't load lkm. Tested-by: glboxed-max Signed-off-by: AlexLiuDev233 --- .github/workflows/build-manager.yml | 1 - kernel/build-all.sh | 9 +- userspace/ksuinit/src/lib.rs | 271 +++++++++++++++++++++++++++- 3 files changed, 269 insertions(+), 12 deletions(-) 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/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 { From 930f61a654f35b98577e5da781fb30f9a1bc678b Mon Sep 17 00:00:00 2001 From: AlexLiuDev233 Date: Sun, 19 Jul 2026 00:36:45 +0800 Subject: [PATCH 5/5] manager: fix colorScheme#surface/background wrongly Color.Transperent Signed-off-by: AlexLiuDev233 --- .../src/main/java/com/resukisu/resukisu/ui/theme/Theme.kt | 6 ------ 1 file changed, 6 deletions(-) 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, - ) - } ) }