fix(tauri): harden UI process restart path resolution and avoid exit on relaunch failure

This fixes Linux-related issue when UI process do not start automatically after upgrade.

- replace direct current_exe relaunch usage with verified launch program resolution
- consider both current_exe and argv0, but only accept verified launchable file paths
- fail relaunch with explicit error when no safe executable path is available
- in reconnect flow, exit current UI only if relaunch spawn succeeds
- if relaunch request fails, keep current UI process running and continue normal startup

https://github.com/safing/portmaster-shadow/issues/40
This commit is contained in:
Alexandr Stelnykovych
2026-04-10 16:01:13 +03:00
parent fab4d3e68b
commit ce67af81e3
2 changed files with 60 additions and 8 deletions
+9 -4
View File
@@ -66,11 +66,16 @@ impl portmaster::Handler for WsHandler {
// relaunch the UI process now that the core is reachable again.
if self.handle.portmaster().consume_restart_ui_proc_requested() {
info!("restart-ui-process pending, relaunching UI process");
if let Err(err) = relaunch::request_ui_relaunch() {
error!("failed to relaunch UI process after upgrade: {}", err);
match relaunch::request_ui_relaunch() {
Ok(()) => {
self.handle.exit(0);
return;
}
Err(err) => {
error!("failed to relaunch UI process after upgrade: {}", err);
error!("continuing with current UI process");
}
}
self.handle.exit(0);
return;
}
// we successfully connected to Portmaster. Set is_first_connect to false
+51 -4
View File
@@ -1,5 +1,6 @@
use std::{
ffi::OsString,
path::Path,
process::{Command, Stdio},
thread,
time::Duration,
@@ -11,6 +12,54 @@ const UI_RELAUNCH_HELPER_ENV: &str = "PORTMASTER_UI_RELAUNCH_HELPER";
const RELAUNCH_RETRY_COUNT: usize = 40;
const RELAUNCH_RETRY_DELAY: Duration = Duration::from_millis(500);
fn current_process_argv0() -> Option<OsString> {
std::env::args_os().next()
}
fn is_usable_launch_program(program: &OsString) -> bool {
let path = Path::new(program);
// Fail closed for command-only values (for example, "portmaster"): we cannot
// verify where they resolve to, so do not use them for relaunch.
if !path.is_absolute() && path.components().count() <= 1 {
return false;
}
if !path.exists() || !path.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = std::fs::metadata(path) {
return meta.permissions().mode() & 0o111 != 0;
}
return false;
}
#[cfg(not(unix))]
{
true
}
}
fn resolve_launch_program() -> Result<OsString, String> {
let current_exe = std::env::current_exe().ok().map(|p| p.into_os_string());
let argv0 = current_process_argv0();
if let Some(program) = current_exe.as_ref().filter(|p| is_usable_launch_program(p)) {
return Ok(program.clone());
}
if let Some(program) = argv0.as_ref().filter(|p| is_usable_launch_program(p)) {
return Ok(program.clone());
}
Err("failed to determine relaunch executable: no verified launchable file path from current_exe or argv0".to_string())
}
fn current_process_args() -> Vec<OsString> {
std::env::args_os()
.skip(1)
@@ -23,8 +72,7 @@ fn current_process_args() -> Vec<OsString> {
}
pub fn request_ui_relaunch() -> Result<(), String> {
let exe = std::env::current_exe()
.map_err(|err| format!("failed to get current executable path: {}", err))?;
let exe = resolve_launch_program()?;
let args = current_process_args();
let mut cmd = Command::new(&exe);
@@ -53,8 +101,7 @@ pub fn run_relaunch_helper_if_requested() {
}
fn run_relaunch_helper() -> Result<(), String> {
let exe = std::env::current_exe()
.map_err(|err| format!("failed to get current executable path in relaunch helper: {}", err))?;
let exe = resolve_launch_program()?;
let args = current_process_args();
debug!("[tauri] relaunch helper started");