Skip to content

Commit

Permalink
Move shared macos code to talpid-macos
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Oct 3, 2024
1 parent 3417535 commit 1743bf5
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 120 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"talpid-core",
"talpid-dbus",
"talpid-future",
"talpid-macos",
"talpid-net",
"talpid-openvpn",
"talpid-openvpn-plugin",
Expand Down
3 changes: 2 additions & 1 deletion talpid-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
72 changes: 5 additions & 67 deletions talpid-core/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use std::{
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::{Path, PathBuf},
str::FromStr,
sync::{Arc, Weak},
time::{Duration, Instant},
Expand Down Expand Up @@ -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] =
Expand Down Expand Up @@ -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<Path>) -> Option<pid_t> {
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<Vec<pid_t>> {
// 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::<pid_t>()) 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<PathBuf> {
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,
Expand Down
2 changes: 1 addition & 1 deletion talpid-core/src/split_tunnel/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
60 changes: 9 additions & 51 deletions talpid-core/src/split_tunnel/macos/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -231,7 +230,7 @@ pub enum ExclusionStatus {

#[derive(Debug)]
struct InnerProcessStates {
processes: HashMap<u32, ProcessInfo>,
processes: HashMap<pid_t, ProcessInfo>,
exclude_paths: HashSet<PathBuf>,
}

Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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<Vec<u32>> {
// 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::<u32>()) 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<PathBuf> {
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,
Expand Down Expand Up @@ -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`.
Expand Down
15 changes: 15 additions & 0 deletions talpid-macos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
7 changes: 7 additions & 0 deletions talpid-macos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Interface with macOS-specific bits.

#![deny(missing_docs)]
#![cfg(target_os = "macos")]

/// Processes
pub mod process;
67 changes: 67 additions & 0 deletions talpid-macos/src/process.rs
Original file line number Diff line number Diff line change
@@ -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<Path>) -> Option<pid_t> {
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<Vec<pid_t>> {
// 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::<pid_t>()) 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<PathBuf> {
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"))?,
))
}

0 comments on commit 1743bf5

Please sign in to comment.