fix(linux): stage approved cores when installing service

Pass available cores and their SHA-256 digests explicitly during service installation and repair, instead of relying on automatic discovery that can skip untrusted source locations. Preserve the arguments across root, pkexec, and sudo execution paths.

Fixes #7995
This commit is contained in:
wonfen
2026-09-22 08:41:55 +08:00
parent 3b958acb22
commit 93b7bb8fe8
2 changed files with 40 additions and 24 deletions
+36 -24
View File
@@ -583,11 +583,11 @@ fn install_service() -> Result<()> {
.iter()
.map(|core| service_core_path(core, ".exe"))
.collect::<Result<Vec<_>>>()?;
install_windows_service_with_cores(&cores, run_windows_service_installer)
install_service_with_cores(&cores, run_windows_service_installer)
}
#[cfg(target_os = "windows")]
fn install_windows_service_with_cores(
#[cfg(any(target_os = "windows", target_os = "linux", test))]
fn install_service_with_cores(
cores: &[PathBuf],
mut run_installer: impl FnMut(&[std::ffi::OsString]) -> Result<()>,
) -> Result<()> {
@@ -712,7 +712,15 @@ fn uninstall_service() -> Result<()> {
#[cfg(target_os = "linux")]
fn install_service() -> Result<()> {
logging!(info, Type::Service, "install service");
let cores = crate::config::IVerge::VALID_CLASH_CORES
.iter()
.map(|core| service_core_path(core, ""))
.collect::<Result<Vec<_>>>()?;
install_service_with_cores(&cores, run_linux_service_installer)
}
#[cfg(target_os = "linux")]
fn run_linux_service_installer(arguments: &[std::ffi::OsString]) -> Result<()> {
let install_path = packaged_service_tool_path("clash-verge-service-install", || {
Ok(tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-install"))
})?;
@@ -723,12 +731,13 @@ fn install_service() -> Result<()> {
let elevator = crate::utils::help::linux_elevator();
let output = if linux_running_as_root() {
StdCommand::new(&install_path).output()?
StdCommand::new(&install_path).args(arguments).output()?
} else {
let result = StdCommand::new(&elevator)
.arg("--disable-internal-agent")
.arg(&install_path)
.output()?;
let mut elevated = StdCommand::new(&elevator);
if elevator.contains("pkexec") {
elevated.arg("--disable-internal-agent");
}
let result = elevated.arg(&install_path).args(arguments).output()?;
// 如果 pkexec 执行失败,回退到 sudo
if !result.status.success() && elevator.contains("pkexec") {
@@ -738,7 +747,7 @@ fn install_service() -> Result<()> {
"pkexec failed with code {}, falling back to sudo",
result.status.code().unwrap_or(-1)
);
StdCommand::new("sudo").arg(&install_path).output()?
StdCommand::new("sudo").arg(&install_path).args(arguments).output()?
} else {
result
}
@@ -2243,20 +2252,19 @@ mod tests {
}
}
#[cfg(target_os = "windows")]
#[test]
fn windows_service_install_combines_service_and_attested_cores() -> anyhow::Result<()> {
fn service_install_combines_service_and_attested_cores() -> anyhow::Result<()> {
use std::ffi::OsString;
let root = TestDirectory::new("scoop install")?;
let root = TestDirectory::new("service install")?;
let cores: Vec<_> = crate::config::IVerge::VALID_CLASH_CORES
.iter()
.map(|name| root.path().join(format!("{name}.exe")))
.map(|name| root.path().join(format!("{name}{}", std::env::consts::EXE_SUFFIX)))
.collect();
for core in &cores {
std::fs::write(core, b"abc")?;
}
let mut calls = Vec::new();
super::install_windows_service_with_cores(&cores, |arguments| {
super::install_service_with_cores(&cores, |arguments| {
calls.push(arguments.to_vec());
Ok(())
})?;
@@ -2275,16 +2283,19 @@ mod tests {
Ok(())
}
#[cfg(target_os = "windows")]
#[test]
fn windows_service_install_skips_unavailable_cores() -> anyhow::Result<()> {
fn service_install_skips_unavailable_cores() -> anyhow::Result<()> {
let root = TestDirectory::new("missing-core")?;
let core = root.path().join("verge-mihomo.exe");
let missing = root.path().join("verge-mihomo-alpha.exe");
let core = root
.path()
.join(format!("verge-mihomo{}", std::env::consts::EXE_SUFFIX));
let missing = root
.path()
.join(format!("verge-mihomo-alpha{}", std::env::consts::EXE_SUFFIX));
std::fs::write(&core, b"abc")?;
let cores = [core.clone(), missing.clone(), root.path().to_path_buf()];
let mut calls = Vec::new();
super::install_windows_service_with_cores(&cores, |arguments| {
super::install_service_with_cores(&cores, |arguments| {
calls.push(arguments.to_vec());
Ok(())
})?;
@@ -2293,7 +2304,7 @@ mod tests {
assert_eq!(calls[0][0], "--install-service");
assert_eq!(calls[0][2], core.as_os_str());
let error = super::install_windows_service_with_cores(&[missing, root.path().to_path_buf()], |_| {
let error = super::install_service_with_cores(&[missing, root.path().to_path_buf()], |_| {
panic!("no available core must fail before invoking the installer")
})
.expect_err("installation requires at least one available core");
@@ -2301,14 +2312,15 @@ mod tests {
Ok(())
}
#[cfg(target_os = "windows")]
#[test]
fn windows_service_install_propagates_installer_failure() -> anyhow::Result<()> {
fn service_install_propagates_installer_failure() -> anyhow::Result<()> {
let root = TestDirectory::new("install-failure")?;
let core = root.path().join("verge-mihomo.exe");
let core = root
.path()
.join(format!("verge-mihomo{}", std::env::consts::EXE_SUFFIX));
std::fs::write(&core, b"abc")?;
let mut calls = 0;
let result = super::install_windows_service_with_cores(std::slice::from_ref(&core), |_| {
let result = super::install_service_with_cores(std::slice::from_ref(&core), |_| {
calls += 1;
bail!("installer failed with exit code 1")
});