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 {