diff --git a/Cargo.lock b/Cargo.lock index a47c1f6..ad7774f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,6 +733,7 @@ version = "0.1.0" dependencies = [ "cc", "core-foundation", + "libc", "log", "psl", "rquickjs-sys", diff --git a/Cargo.toml b/Cargo.toml index 7f6f047..9ac559b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ log = "0.4" tokio = { version = "1", optional = true, default-features = false, features = ["sync"] } # Public Suffix List used to stop the WPAD suffix walk at the registrable domain. psl = "2" + # Runtime for the sandboxed PAC backend, in AOT mode only: `runtime` + `std` # but deliberately NO `cranelift` (or `winch`/`pulley`), so this build cannot # compile wasm at all — it can only `Module::deserialize` the artifact that @@ -116,6 +117,9 @@ windows-sys = { version = "0.60", features = [ # non-Windows dependency above. rquickjs-sys = { version = "0.12.1", optional = true } +[target.'cfg(target_os = "linux")'.dependencies] +libc = "0.2" + [dev-dependencies] tokio = { version = "1", features = ["sync", "rt", "macros", "time"] } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 7caac0c..7fe5f07 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -20,6 +20,7 @@ use crate::bypass::BypassRules; use crate::types::{LinuxProxyConfig, PlatformProxyConfig, ProxyKind}; use std::collections::HashMap; use std::io::BufRead; +use std::os::unix::process::CommandExt; use std::process::{Child, Command, Stdio}; use std::sync::{Arc, Mutex}; @@ -128,19 +129,15 @@ pub(crate) struct Watcher { } pub(crate) fn spawn_watcher(on_change: Arc) -> Watcher { - let mut spawned = Command::new("dconf") - .args(["watch", "/system/proxy/"]) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn(); + let mut dconf = Command::new("dconf"); + dconf.args(["watch", "/system/proxy/"]); + configure_watcher_command(&mut dconf); + let mut spawned = dconf.spawn(); if spawned.is_err() { - spawned = Command::new("gsettings") - .args(["monitor", "org.gnome.system.proxy"]) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn(); + let mut gsettings = Command::new("gsettings"); + gsettings.args(["monitor", "org.gnome.system.proxy"]); + configure_watcher_command(&mut gsettings); + spawned = gsettings.spawn(); } let Ok(mut child) = spawned else { log::debug!( @@ -173,6 +170,30 @@ pub(crate) fn spawn_watcher(on_change: Arc) -> Watcher { Watcher { child, thread } } +/// Configure a proxy watcher to terminate if its owning process exits without dropping it. +fn configure_watcher_command(command: &mut Command) { + let expected_parent = std::process::id() as libc::pid_t; + command + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()); + + // SAFETY: pre_exec runs after fork in the single-threaded child. prctl and + // getppid are async-signal-safe Linux system calls and do not access Rust state. + unsafe { + command.pre_exec(move || { + if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) == -1 { + return Err(std::io::Error::last_os_error()); + } + // The parent may have exited between fork and PR_SET_PDEATHSIG. + if libc::getppid() != expected_parent { + libc::raise(libc::SIGTERM); + } + Ok(()) + }); + } +} + impl Drop for Watcher { fn drop(&mut self) { if let Some(mut child) = self.child.lock().unwrap_or_else(|e| e.into_inner()).take() {