From 1743bf5c673a09f62cf63475a0a9e8e1ad88f83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Tue, 1 Oct 2024 10:59:45 +0200 Subject: [PATCH] Move shared macos code to talpid-macos --- Cargo.lock | 9 +++ Cargo.toml | 1 + talpid-core/Cargo.toml | 3 +- talpid-core/src/resolver.rs | 72 ++----------------- talpid-core/src/split_tunnel/macos/mod.rs | 2 +- talpid-core/src/split_tunnel/macos/process.rs | 60 +++------------- talpid-macos/Cargo.toml | 15 ++++ talpid-macos/src/lib.rs | 7 ++ talpid-macos/src/process.rs | 67 +++++++++++++++++ 9 files changed, 116 insertions(+), 120 deletions(-) create mode 100644 talpid-macos/Cargo.toml create mode 100644 talpid-macos/src/lib.rs create mode 100644 talpid-macos/src/process.rs diff --git a/Cargo.lock b/Cargo.lock index d07de1332cdb..c34a60d2fb57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4107,6 +4107,7 @@ dependencies = [ "subslice", "system-configuration", "talpid-dbus", + "talpid-macos", "talpid-net", "talpid-openvpn", "talpid-platform-metadata", @@ -4149,6 +4150,14 @@ dependencies = [ "tokio", ] +[[package]] +name = "talpid-macos" +version = "0.0.0" +dependencies = [ + "libc", + "log", +] + [[package]] name = "talpid-net" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 3157d70d1bea..32757afc7881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "talpid-core", "talpid-dbus", "talpid-future", + "talpid-macos", "talpid-net", "talpid-openvpn", "talpid-openvpn-plugin", diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 5fab3022aba3..a0ddb45825da 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -56,9 +56,10 @@ talpid-platform-metadata = { path = "../talpid-platform-metadata" } pcap = { version = "2.1", features = ["capture-stream"] } pnet_packet = "0.34" tun = { version = "0.5.5", features = ["async"] } -nix = { version = "0.28", features = ["socket"] } +nix = { version = "0.28", features = ["socket", "signal"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +talpid-macos = { path = "../talpid-macos" } talpid-net = { path = "../talpid-net" } [target.'cfg(windows)'.dependencies] diff --git a/talpid-core/src/resolver.rs b/talpid-core/src/resolver.rs index 0d078888777c..d4900a6e1476 100644 --- a/talpid-core/src/resolver.rs +++ b/talpid-core/src/resolver.rs @@ -7,7 +7,6 @@ use std::{ io, net::{IpAddr, Ipv4Addr, SocketAddr}, - path::{Path, PathBuf}, str::FromStr, sync::{Arc, Weak}, time::{Duration, Instant}, @@ -40,7 +39,6 @@ use hickory_server::{ server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, ServerFuture, }; -use libc::{c_void, kill, pid_t, proc_listallpids, proc_pidpath, SIGHUP}; use std::sync::LazyLock; const ALLOWED_RECORD_TYPES: &[RecordType] = @@ -358,75 +356,15 @@ const MDNS_RESPONDER_PATH: &str = "/usr/sbin/mDNSResponder"; /// Find and kill mDNSResponder. The OS will restart the service. fn kill_mdnsresponder() -> io::Result<()> { - if let Some(mdns_pid) = pid_of_path(MDNS_RESPONDER_PATH) { - if unsafe { kill(mdns_pid as i32, SIGHUP) } != 0 { - return Err(io::Error::last_os_error()); - } + if let Some(mdns_pid) = talpid_macos::process::pid_of_path(MDNS_RESPONDER_PATH) { + nix::sys::signal::kill( + nix::unistd::Pid::from_raw(mdns_pid), + nix::sys::signal::SIGHUP, + )?; } Ok(()) } -/// Return the first process identifier matching a specified path, if one exists. -fn pid_of_path(find_path: impl AsRef) -> Option { - match list_pids() { - Ok(pids) => { - for pid in pids { - if let Ok(path) = process_path(pid) { - if path == find_path.as_ref() { - return Some(pid); - } - } - } - None - } - Err(error) => { - log::error!("Failed to list processes: {error}"); - None - } - } -} - -/// Obtain a list of all pids -fn list_pids() -> io::Result> { - // SAFETY: Passing in null and 0 returns the number of processes - let num_pids = unsafe { proc_listallpids(std::ptr::null_mut(), 0) }; - if num_pids <= 0 { - return Err(io::Error::last_os_error()); - } - let num_pids = usize::try_from(num_pids).unwrap(); - let mut pids = vec![0i32; num_pids]; - - let buf_sz = (num_pids * std::mem::size_of::()) as i32; - // SAFETY: 'pids' is large enough to contain 'num_pids' processes - let num_pids = unsafe { proc_listallpids(pids.as_mut_ptr() as *mut c_void, buf_sz) }; - if num_pids == -1 { - return Err(io::Error::last_os_error()); - } - - pids.resize(usize::try_from(num_pids).unwrap(), 0); - - Ok(pids) -} - -fn process_path(pid: pid_t) -> io::Result { - let mut buffer = [0u8; libc::MAXPATHLEN as usize]; - // SAFETY: `proc_pidpath` returns at most `buffer.len()` bytes - let buf_len = unsafe { - proc_pidpath( - pid, - buffer.as_mut_ptr() as *mut c_void, - u32::try_from(buffer.len()).unwrap(), - ) - }; - if buf_len == -1 { - return Err(io::Error::last_os_error()); - } - Ok(PathBuf::from( - std::str::from_utf8(&buffer[0..buf_len as usize]) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid process path"))?, - )) -} - type LookupResponse<'a> = MessageResponse< 'a, 'a, diff --git a/talpid-core/src/split_tunnel/macos/mod.rs b/talpid-core/src/split_tunnel/macos/mod.rs index b3acdb01361c..38c4201ea071 100644 --- a/talpid-core/src/split_tunnel/macos/mod.rs +++ b/talpid-core/src/split_tunnel/macos/mod.rs @@ -614,7 +614,7 @@ impl State { Some(vpn_interface.clone()), route_manager.clone(), Box::new(move |packet| { - match states.get_process_status(packet.header.pth_pid as u32) { + match states.get_process_status(packet.header.pth_pid) { ExclusionStatus::Excluded => tun::RoutingDecision::DefaultInterface, ExclusionStatus::Included => tun::RoutingDecision::VpnTunnel, ExclusionStatus::Unknown => { diff --git a/talpid-core/src/split_tunnel/macos/process.rs b/talpid-core/src/split_tunnel/macos/process.rs index 52e0fb1862b0..7069e5fdd828 100644 --- a/talpid-core/src/split_tunnel/macos/process.rs +++ b/talpid-core/src/split_tunnel/macos/process.rs @@ -6,18 +6,17 @@ //! Endpoint Security framework. use futures::channel::oneshot; -use libc::{proc_listallpids, proc_pidpath}; +use libc::pid_t; use serde::Deserialize; use std::{ collections::{HashMap, HashSet}, - ffi::c_void, io, path::PathBuf, process::Stdio, - ptr, sync::{Arc, LazyLock, Mutex}, time::Duration, }; +use talpid_macos::process::{list_pids, process_path}; use talpid_platform_metadata::MacosVersion; use talpid_types::tunnel::ErrorStateCause; use tokio::io::{AsyncBufReadExt, BufReader}; @@ -52,7 +51,7 @@ pub enum Error { InitializePids(#[source] io::Error), /// Failed to find path for a process #[error("Failed to find path for a process: {}", _0)] - FindProcessPath(#[source] io::Error, u32), + FindProcessPath(#[source] io::Error, pid_t), } impl From<&Error> for ErrorStateCause { @@ -231,7 +230,7 @@ pub enum ExclusionStatus { #[derive(Debug)] struct InnerProcessStates { - processes: HashMap, + processes: HashMap, exclude_paths: HashSet, } @@ -277,7 +276,7 @@ impl ProcessStates { inner.exclude_paths = paths; } - pub fn get_process_status(&self, pid: u32) -> ExclusionStatus { + pub fn get_process_status(&self, pid: pid_t) -> ExclusionStatus { let inner = self.inner.lock().unwrap(); match inner.processes.get(&pid) { Some(val) if val.is_excluded() => ExclusionStatus::Excluded, @@ -300,7 +299,7 @@ impl InnerProcessStates { // For new processes, inherit all exclusion state from the parent, if there is one. // Otherwise, look up excluded paths - fn handle_fork(&mut self, parent_pid: u32, exec_path: PathBuf, msg: ESForkEvent) { + fn handle_fork(&mut self, parent_pid: pid_t, exec_path: PathBuf, msg: ESForkEvent) { let pid = msg.child.audit_token.pid; if self.processes.contains_key(&pid) { @@ -327,7 +326,7 @@ impl InnerProcessStates { self.processes.insert(pid, base_info); } - fn handle_exec(&mut self, pid: u32, msg: ESExecEvent) { + fn handle_exec(&mut self, pid: pid_t, msg: ESExecEvent) { let Some(info) = self.processes.get_mut(&pid) else { log::error!("exec received for unknown pid {pid}"); return; @@ -354,54 +353,13 @@ impl InnerProcessStates { } } - fn handle_exit(&mut self, pid: u32) { + fn handle_exit(&mut self, pid: pid_t) { if self.processes.remove(&pid).is_none() { log::error!("exit syscall for unknown pid {pid}"); } } } -/// Obtain a list of all pids -fn list_pids() -> io::Result> { - // SAFETY: Passing in null and 0 returns the number of processes - let num_pids = unsafe { proc_listallpids(ptr::null_mut(), 0) }; - if num_pids <= 0 { - return Err(io::Error::last_os_error()); - } - let num_pids = usize::try_from(num_pids).unwrap(); - let mut pids = vec![0u32; num_pids]; - - let buf_sz = (num_pids * std::mem::size_of::()) as i32; - // SAFETY: 'pids' is large enough to contain 'num_pids' processes - let num_pids = unsafe { proc_listallpids(pids.as_mut_ptr() as *mut c_void, buf_sz) }; - if num_pids == -1 { - return Err(io::Error::last_os_error()); - } - - pids.resize(usize::try_from(num_pids).unwrap(), 0); - - Ok(pids) -} - -fn process_path(pid: u32) -> io::Result { - let mut buffer = [0u8; libc::MAXPATHLEN as usize]; - // SAFETY: `proc_pidpath` returns at most `buffer.len()` bytes - let buf_len = unsafe { - proc_pidpath( - pid as i32, - buffer.as_mut_ptr() as *mut c_void, - buffer.len() as u32, - ) - }; - if buf_len == -1 { - return Err(io::Error::last_os_error()); - } - Ok(PathBuf::from( - std::str::from_utf8(&buffer[0..buf_len as usize]) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid process path"))?, - )) -} - #[derive(Debug, Clone)] struct ProcessInfo { exec_path: PathBuf, @@ -480,7 +438,7 @@ struct ESExecutable { /// https://developer.apple.com/documentation/endpointsecurity/es_process_t/3228975-audit_token?language=objc #[derive(Debug, Deserialize)] struct ESAuditToken { - pid: u32, + pid: pid_t, } /// Process information for the message returned by `eslogger`. diff --git a/talpid-macos/Cargo.toml b/talpid-macos/Cargo.toml new file mode 100644 index 000000000000..7868707b4ca3 --- /dev/null +++ b/talpid-macos/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "talpid-macos" +description = "Abstractions for macOS" +authors.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[target.'cfg(target_os="macos")'.dependencies] +libc = "0.2" +log = { workspace = true } diff --git a/talpid-macos/src/lib.rs b/talpid-macos/src/lib.rs new file mode 100644 index 000000000000..964dd689e0af --- /dev/null +++ b/talpid-macos/src/lib.rs @@ -0,0 +1,7 @@ +//! Interface with macOS-specific bits. + +#![deny(missing_docs)] +#![cfg(target_os = "macos")] + +/// Processes +pub mod process; diff --git a/talpid-macos/src/process.rs b/talpid-macos/src/process.rs new file mode 100644 index 000000000000..69722e054ceb --- /dev/null +++ b/talpid-macos/src/process.rs @@ -0,0 +1,67 @@ +use libc::{c_void, pid_t, proc_listallpids, proc_pidpath}; +use std::{ + io, + path::{Path, PathBuf}, +}; + +/// Return the first process identifier matching a specified path, if one exists. +pub fn pid_of_path(find_path: impl AsRef) -> Option { + match list_pids() { + Ok(pids) => { + for pid in pids { + if let Ok(path) = process_path(pid) { + if path == find_path.as_ref() { + return Some(pid); + } + } + } + None + } + Err(error) => { + log::error!("Failed to list processes: {error}"); + None + } + } +} + +/// Obtain a list of all process identifiers +pub fn list_pids() -> io::Result> { + // SAFETY: Passing in null and 0 returns the number of processes + let num_pids = unsafe { proc_listallpids(std::ptr::null_mut(), 0) }; + if num_pids <= 0 { + return Err(io::Error::last_os_error()); + } + let num_pids = usize::try_from(num_pids).unwrap(); + let mut pids = vec![0i32; num_pids]; + + let buf_sz = (num_pids * std::mem::size_of::()) as i32; + // SAFETY: 'pids' is large enough to contain 'num_pids' processes + let num_pids = unsafe { proc_listallpids(pids.as_mut_ptr() as *mut c_void, buf_sz) }; + if num_pids == -1 { + return Err(io::Error::last_os_error()); + } + + pids.resize(usize::try_from(num_pids).unwrap(), 0); + + Ok(pids) +} + +/// Return the path of the process `pid` +pub fn process_path(pid: pid_t) -> io::Result { + let mut buffer = [0u8; libc::MAXPATHLEN as usize]; + // SAFETY: `proc_pidpath` returns at most `buffer.len()` bytes + let buf_len = unsafe { + proc_pidpath( + pid, + buffer.as_mut_ptr() as *mut c_void, + u32::try_from(buffer.len()).unwrap(), + ) + }; + if buf_len == -1 { + return Err(io::Error::last_os_error()); + } + Ok(PathBuf::from( + std::str::from_utf8(&buffer[0..buf_len as usize]) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid process path"))?, + )) +}