Files
SukiSU-Ultra/userspace/ksud/src/feature.rs
生于生时 亡于亡刻 d0e8faea77 chore(ksud): enable clippy::all, clippy::pedantic && make clippy happy (#617)
* Revert "chore(ksud): bump ksud's deps (#585)"
* Because it may cause compilation errors.

This reverts commit c8020b2066.

* chore(ksud): remove unused Result

Signed-off-by: Tools-app <localhost.hutao@gmail.com>

* chore(ksud): enable clippy::all, clippy::pedantic && make clippy happy

https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or

https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args

https://rust-lang.github.io/rust-clippy/master/index.html#used_underscore_items

https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls

https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate
...
and use some #![allow(...)] or #[allow(...)]

Signed-off-by: Tools-app <localhost.hutao@gmail.com>

---------

Signed-off-by: Tools-app <localhost.hutao@gmail.com>
2025-11-22 06:09:45 +08:00

439 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use anyhow::{Context, Result, bail};
use const_format::concatcp;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use crate::defs;
const FEATURE_CONFIG_PATH: &str = concatcp!(defs::WORKING_DIR, ".feature_config");
#[allow(clippy::unreadable_literal)]
const FEATURE_MAGIC: u32 = 0x7f4b5355;
const FEATURE_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FeatureId {
SuCompat = 0,
KernelUmount = 1,
EnhancedSecurity = 2,
SuLog = 3,
}
impl FeatureId {
pub const fn from_u32(id: u32) -> Option<Self> {
match id {
0 => Some(Self::SuCompat),
1 => Some(Self::KernelUmount),
2 => Some(Self::EnhancedSecurity),
3 => Some(Self::SuLog),
_ => None,
}
}
pub const fn name(self) -> &'static str {
match self {
Self::SuCompat => "su_compat",
Self::KernelUmount => "kernel_umount",
Self::EnhancedSecurity => "enhanced_security",
Self::SuLog => "sulog",
}
}
pub const fn description(self) -> &'static str {
match self {
Self::SuCompat => {
"SU Compatibility Mode - allows authorized apps to gain root via traditional 'su' command"
}
Self::KernelUmount => {
"Kernel Umount - controls whether kernel automatically unmounts modules when not needed"
}
Self::EnhancedSecurity => {
"Enhanced Security - disable nonKSU root elevation and unauthorized UID downgrades"
}
Self::SuLog => {
"SU Log - enables logging of SU command usage to kernel log for auditing purposes"
}
}
}
}
fn parse_feature_id(name: &str) -> Result<FeatureId> {
match name {
"su_compat" | "0" => Ok(FeatureId::SuCompat),
"kernel_umount" | "1" => Ok(FeatureId::KernelUmount),
"enhanced_security" | "2" => Ok(FeatureId::EnhancedSecurity),
"sulog" | "3" => Ok(FeatureId::SuLog),
_ => bail!("Unknown feature: {name}"),
}
}
pub fn load_binary_config() -> Result<HashMap<u32, u64>> {
let path = Path::new(FEATURE_CONFIG_PATH);
if !path.exists() {
log::info!("Feature config not found, using defaults");
return Ok(HashMap::new());
}
let mut file = File::open(path).with_context(|| "Failed to open feature config")?;
let mut magic_buf = [0u8; 4];
file.read_exact(&mut magic_buf)
.with_context(|| "Failed to read magic")?;
let magic = u32::from_le_bytes(magic_buf);
if magic != FEATURE_MAGIC {
bail!("Invalid feature config magic: expected 0x{FEATURE_MAGIC:08x}, got 0x{magic:08x}",);
}
let mut version_buf = [0u8; 4];
file.read_exact(&mut version_buf)
.with_context(|| "Failed to read version")?;
let version = u32::from_le_bytes(version_buf);
if version != FEATURE_VERSION {
log::warn!(
"Feature config version mismatch: expected {FEATURE_VERSION}, got {version
}",
);
}
let mut count_buf = [0u8; 4];
file.read_exact(&mut count_buf)
.with_context(|| "Failed to read count")?;
let count = u32::from_le_bytes(count_buf);
let mut features = HashMap::new();
for _ in 0..count {
let mut id_buf = [0u8; 4];
let mut value_buf = [0u8; 8];
file.read_exact(&mut id_buf)
.with_context(|| "Failed to read feature id")?;
file.read_exact(&mut value_buf)
.with_context(|| "Failed to read feature value")?;
let id = u32::from_le_bytes(id_buf);
let value = u64::from_le_bytes(value_buf);
features.insert(id, value);
}
log::info!("Loaded {} features from config", features.len());
Ok(features)
}
pub fn save_binary_config(features: &HashMap<u32, u64>) -> Result<()> {
crate::utils::ensure_dir_exists(Path::new(defs::WORKING_DIR))?;
let path = Path::new(FEATURE_CONFIG_PATH);
let mut file = File::create(path).with_context(|| "Failed to create feature config")?;
file.write_all(&FEATURE_MAGIC.to_le_bytes())
.with_context(|| "Failed to write magic")?;
file.write_all(&FEATURE_VERSION.to_le_bytes())
.with_context(|| "Failed to write version")?;
let count = features.len() as u32;
file.write_all(&count.to_le_bytes())
.with_context(|| "Failed to write count")?;
for (&id, &value) in features {
file.write_all(&id.to_le_bytes())
.with_context(|| format!("Failed to write feature id {id}"))?;
file.write_all(&value.to_le_bytes())
.with_context(|| format!("Failed to write feature value for id {id}"))?;
}
file.sync_all()
.with_context(|| "Failed to sync feature config")?;
log::info!("Saved {} features to config", features.len());
Ok(())
}
pub fn apply_config(features: &HashMap<u32, u64>) {
log::info!("Applying feature configuration to kernel...");
let mut applied = 0;
for (&id, &value) in features {
match crate::ksucalls::set_feature(id, value) {
Ok(()) => {
if let Some(feature_id) = FeatureId::from_u32(id) {
log::info!("Set feature {} to {value}", feature_id.name());
} else {
log::info!("Set feature {id} to {value}");
}
applied += 1;
}
Err(e) => {
log::warn!("Failed to set feature {id}: {e}");
}
}
}
log::info!("Applied {applied} features successfully");
}
pub fn get_feature(id: &str) -> Result<()> {
let feature_id = parse_feature_id(id)?;
let (value, supported) = crate::ksucalls::get_feature(feature_id as u32)
.with_context(|| format!("Failed to get feature {id}"))?;
if !supported {
println!("Feature '{id}' is not supported by kernel");
return Ok(());
}
println!("Feature: {} ({})", feature_id.name(), feature_id as u32);
println!("Description: {}", feature_id.description());
println!("Value: {value}");
println!(
"Status: {}",
if value != 0 { "enabled" } else { "disabled" }
);
Ok(())
}
pub fn set_feature(id: &str, value: u64) -> Result<()> {
let feature_id = parse_feature_id(id)?;
// Check if this feature is managed by any module
if let Ok(managed_features_map) = crate::module::get_managed_features() {
// Find which modules manage this feature
let managing_modules: Vec<&String> = managed_features_map
.iter()
.filter(|(_, features)| features.iter().any(|f| f == feature_id.name()))
.map(|(module_id, _)| module_id)
.collect();
if !managing_modules.is_empty() {
// Feature is managed, check if caller is an authorized module
let caller_module = std::env::var("KSU_MODULE").unwrap_or_default();
if caller_module.is_empty() || !managing_modules.contains(&&caller_module) {
bail!(
"Feature '{}' is managed by module(s): {}. Direct modification is not allowed.",
feature_id.name(),
managing_modules
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
log::info!(
"Module '{caller_module}' is setting managed feature '{}'",
feature_id.name()
);
}
}
crate::ksucalls::set_feature(feature_id as u32, value)
.with_context(|| format!("Failed to set feature {id} to {value}"))?;
println!(
"Feature '{}' set to {value} ({})",
feature_id.name(),
if value != 0 { "enabled" } else { "disabled" }
);
Ok(())
}
pub fn list_features() {
println!("Available Features:");
println!("{}", "=".repeat(80));
// Get managed features from modules
let managed_features_map = crate::module::get_managed_features().unwrap_or_default();
// Build a reverse map: feature_name -> Vec<module_id>
let mut feature_to_modules: HashMap<String, Vec<String>> = HashMap::new();
for (module_id, feature_list) in &managed_features_map {
for feature_name in feature_list {
feature_to_modules
.entry(feature_name.clone())
.or_default()
.push(module_id.clone());
}
}
let all_features = [
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
FeatureId::SuLog,
];
for feature_id in &all_features {
let id = *feature_id as u32;
let (value, supported) = crate::ksucalls::get_feature(id).unwrap_or((0, false));
let status = if !supported {
"NOT_SUPPORTED".to_string()
} else if value != 0 {
format!("ENABLED ({value})")
} else {
"DISABLED".to_string()
};
let managed_by = feature_to_modules.get(feature_id.name());
let managed_mark = if managed_by.is_some() {
" [MODULE_MANAGED]"
} else {
""
};
println!(
"[{}] {} (ID={}){}",
status,
feature_id.name(),
id,
managed_mark
);
println!(" {}", feature_id.description());
if let Some(modules) = managed_by {
println!(
" ⚠️ Managed by module(s): {} (forced to 0 on initialization)",
modules.join(", ")
);
}
println!();
}
}
pub fn load_config_and_apply() -> Result<()> {
let features = load_binary_config()?;
if features.is_empty() {
println!("No features found in config file");
return Ok(());
}
apply_config(&features);
println!("Feature configuration loaded and applied");
Ok(())
}
pub fn save_config() -> Result<()> {
let mut features = HashMap::new();
let all_features = [
FeatureId::SuCompat,
FeatureId::KernelUmount,
FeatureId::EnhancedSecurity,
FeatureId::SuLog,
];
for feature_id in &all_features {
let id = *feature_id as u32;
if let Ok((value, supported)) = crate::ksucalls::get_feature(id)
&& supported
{
features.insert(id, value);
log::info!("Saved feature {} = {value}", feature_id.name());
}
}
save_binary_config(&features)?;
println!(
"Current feature states saved to config file ({} features)",
features.len()
);
Ok(())
}
pub fn check_feature(id: &str) -> Result<()> {
let feature_id = parse_feature_id(id)?;
// Check if this feature is managed by any module
let managed_features_map = crate::module::get_managed_features().unwrap_or_default();
let is_managed = managed_features_map
.values()
.any(|features| features.iter().any(|f| f == feature_id.name()));
if is_managed {
println!("managed");
return Ok(());
}
// Check if the feature is supported by kernel
let (_value, supported) = crate::ksucalls::get_feature(feature_id as u32)
.with_context(|| format!("Failed to get feature {id}"))?;
if supported {
println!("supported");
} else {
println!("unsupported");
}
Ok(())
}
pub fn init_features() -> Result<()> {
log::info!("Initializing features from config...");
let mut features = load_binary_config()?;
// Get managed features from active modules and skip them during init
if let Ok(managed_features_map) = crate::module::get_managed_features() {
if !managed_features_map.is_empty() {
log::info!(
"Found {} modules managing features",
managed_features_map.len()
);
// Build a set of all managed feature IDs to skip
for (module_id, feature_list) in &managed_features_map {
log::info!(
"Module '{module_id}' manages {} feature(s)",
feature_list.len()
);
for feature_name in feature_list {
if let Ok(feature_id) = parse_feature_id(feature_name) {
let feature_id_u32 = feature_id as u32;
// Remove managed features from config, let modules control them
if features.remove(&feature_id_u32).is_some() {
log::info!(
" - Skipping managed feature '{feature_name}' (controlled by module: {module_id})",
);
} else {
log::info!(
" - Feature '{feature_name}' is managed by module '{module_id}', skipping",
);
}
} else {
log::warn!(
" - Unknown managed feature '{feature_name}' from module '{module_id}', ignoring",
);
}
}
}
}
} else {
log::warn!(
"Failed to get managed features from modules, continuing with normal initialization"
);
}
if features.is_empty() {
log::info!("No features to apply, skipping initialization");
return Ok(());
}
apply_config(&features);
// Save the configuration (excluding managed features)
save_binary_config(&features)?;
log::info!("Saved feature configuration to file");
Ok(())
}