diff options
| author | David Lönnhager <david.l@mullvad.net> | 2025-04-17 15:06:33 +0200 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2025-04-23 13:03:54 +0200 |
| commit | 1c4657b3cd565dbc14747c69dc416c020103ad88 (patch) | |
| tree | 2bab61bd7243bdc038bbf470202b2af041a05723 | |
| parent | 02a2dad77573ae9fc2c538f92c6fd30959b3eff4 (diff) | |
| download | mullvadvpn-1c4657b3cd565dbc14747c69dc416c020103ad88.tar.xz mullvadvpn-1c4657b3cd565dbc14747c69dc416c020103ad88.zip | |
Add more process helper functions to talpid-macos
| -rw-r--r-- | Cargo.lock | 4 | ||||
| -rw-r--r-- | talpid-macos/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-macos/src/apsl-header | 27 | ||||
| -rw-r--r-- | talpid-macos/src/bindings.rs | 192 | ||||
| -rwxr-xr-x | talpid-macos/src/generate-bindings.sh | 20 | ||||
| -rw-r--r-- | talpid-macos/src/lib.rs | 4 | ||||
| -rw-r--r-- | talpid-macos/src/process.rs | 95 |
7 files changed, 340 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock index ea1474a865..046d9e0b42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2364,9 +2364,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdbus-sys" diff --git a/talpid-macos/Cargo.toml b/talpid-macos/Cargo.toml index 7868707b4c..7b910f5e6d 100644 --- a/talpid-macos/Cargo.toml +++ b/talpid-macos/Cargo.toml @@ -11,5 +11,5 @@ rust-version.workspace = true workspace = true [target.'cfg(target_os="macos")'.dependencies] -libc = "0.2" +libc = "0.2.172" log = { workspace = true } diff --git a/talpid-macos/src/apsl-header b/talpid-macos/src/apsl-header new file mode 100644 index 0000000000..eebfa36fa6 --- /dev/null +++ b/talpid-macos/src/apsl-header @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Mullvad VPN AB. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ diff --git a/talpid-macos/src/bindings.rs b/talpid-macos/src/bindings.rs new file mode 100644 index 0000000000..4076cd2c29 --- /dev/null +++ b/talpid-macos/src/bindings.rs @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2025 Mullvad VPN AB. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* automatically generated by rust-bindgen 0.70.1 */ + +pub const PROX_FDTYPE_VNODE: u32 = 1; +pub const PROC_PIDFDVNODEPATHINFO: u32 = 2; +pub type __uint32_t = ::std::os::raw::c_uint; +pub type __int64_t = ::std::os::raw::c_longlong; +pub type __darwin_gid_t = __uint32_t; +pub type __darwin_off_t = __int64_t; +pub type __darwin_uid_t = __uint32_t; +pub type gid_t = __darwin_gid_t; +pub type off_t = __darwin_off_t; +pub type uid_t = __darwin_uid_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct fsid { + pub val: [i32; 2usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of fsid"][::std::mem::size_of::<fsid>() - 8usize]; + ["Alignment of fsid"][::std::mem::align_of::<fsid>() - 4usize]; + ["Offset of field: fsid::val"][::std::mem::offset_of!(fsid, val) - 0usize]; +}; +pub type fsid_t = fsid; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct proc_fileinfo { + pub fi_openflags: u32, + pub fi_status: u32, + pub fi_offset: off_t, + pub fi_type: i32, + pub fi_guardflags: u32, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of proc_fileinfo"][::std::mem::size_of::<proc_fileinfo>() - 24usize]; + ["Alignment of proc_fileinfo"][::std::mem::align_of::<proc_fileinfo>() - 8usize]; + ["Offset of field: proc_fileinfo::fi_openflags"] + [::std::mem::offset_of!(proc_fileinfo, fi_openflags) - 0usize]; + ["Offset of field: proc_fileinfo::fi_status"] + [::std::mem::offset_of!(proc_fileinfo, fi_status) - 4usize]; + ["Offset of field: proc_fileinfo::fi_offset"] + [::std::mem::offset_of!(proc_fileinfo, fi_offset) - 8usize]; + ["Offset of field: proc_fileinfo::fi_type"] + [::std::mem::offset_of!(proc_fileinfo, fi_type) - 16usize]; + ["Offset of field: proc_fileinfo::fi_guardflags"] + [::std::mem::offset_of!(proc_fileinfo, fi_guardflags) - 20usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vinfo_stat { + pub vst_dev: u32, + pub vst_mode: u16, + pub vst_nlink: u16, + pub vst_ino: u64, + pub vst_uid: uid_t, + pub vst_gid: gid_t, + pub vst_atime: i64, + pub vst_atimensec: i64, + pub vst_mtime: i64, + pub vst_mtimensec: i64, + pub vst_ctime: i64, + pub vst_ctimensec: i64, + pub vst_birthtime: i64, + pub vst_birthtimensec: i64, + pub vst_size: off_t, + pub vst_blocks: i64, + pub vst_blksize: i32, + pub vst_flags: u32, + pub vst_gen: u32, + pub vst_rdev: u32, + pub vst_qspare: [i64; 2usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of vinfo_stat"][::std::mem::size_of::<vinfo_stat>() - 136usize]; + ["Alignment of vinfo_stat"][::std::mem::align_of::<vinfo_stat>() - 8usize]; + ["Offset of field: vinfo_stat::vst_dev"][::std::mem::offset_of!(vinfo_stat, vst_dev) - 0usize]; + ["Offset of field: vinfo_stat::vst_mode"] + [::std::mem::offset_of!(vinfo_stat, vst_mode) - 4usize]; + ["Offset of field: vinfo_stat::vst_nlink"] + [::std::mem::offset_of!(vinfo_stat, vst_nlink) - 6usize]; + ["Offset of field: vinfo_stat::vst_ino"][::std::mem::offset_of!(vinfo_stat, vst_ino) - 8usize]; + ["Offset of field: vinfo_stat::vst_uid"][::std::mem::offset_of!(vinfo_stat, vst_uid) - 16usize]; + ["Offset of field: vinfo_stat::vst_gid"][::std::mem::offset_of!(vinfo_stat, vst_gid) - 20usize]; + ["Offset of field: vinfo_stat::vst_atime"] + [::std::mem::offset_of!(vinfo_stat, vst_atime) - 24usize]; + ["Offset of field: vinfo_stat::vst_atimensec"] + [::std::mem::offset_of!(vinfo_stat, vst_atimensec) - 32usize]; + ["Offset of field: vinfo_stat::vst_mtime"] + [::std::mem::offset_of!(vinfo_stat, vst_mtime) - 40usize]; + ["Offset of field: vinfo_stat::vst_mtimensec"] + [::std::mem::offset_of!(vinfo_stat, vst_mtimensec) - 48usize]; + ["Offset of field: vinfo_stat::vst_ctime"] + [::std::mem::offset_of!(vinfo_stat, vst_ctime) - 56usize]; + ["Offset of field: vinfo_stat::vst_ctimensec"] + [::std::mem::offset_of!(vinfo_stat, vst_ctimensec) - 64usize]; + ["Offset of field: vinfo_stat::vst_birthtime"] + [::std::mem::offset_of!(vinfo_stat, vst_birthtime) - 72usize]; + ["Offset of field: vinfo_stat::vst_birthtimensec"] + [::std::mem::offset_of!(vinfo_stat, vst_birthtimensec) - 80usize]; + ["Offset of field: vinfo_stat::vst_size"] + [::std::mem::offset_of!(vinfo_stat, vst_size) - 88usize]; + ["Offset of field: vinfo_stat::vst_blocks"] + [::std::mem::offset_of!(vinfo_stat, vst_blocks) - 96usize]; + ["Offset of field: vinfo_stat::vst_blksize"] + [::std::mem::offset_of!(vinfo_stat, vst_blksize) - 104usize]; + ["Offset of field: vinfo_stat::vst_flags"] + [::std::mem::offset_of!(vinfo_stat, vst_flags) - 108usize]; + ["Offset of field: vinfo_stat::vst_gen"] + [::std::mem::offset_of!(vinfo_stat, vst_gen) - 112usize]; + ["Offset of field: vinfo_stat::vst_rdev"] + [::std::mem::offset_of!(vinfo_stat, vst_rdev) - 116usize]; + ["Offset of field: vinfo_stat::vst_qspare"] + [::std::mem::offset_of!(vinfo_stat, vst_qspare) - 120usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vnode_info { + pub vi_stat: vinfo_stat, + pub vi_type: ::std::os::raw::c_int, + pub vi_pad: ::std::os::raw::c_int, + pub vi_fsid: fsid_t, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of vnode_info"][::std::mem::size_of::<vnode_info>() - 152usize]; + ["Alignment of vnode_info"][::std::mem::align_of::<vnode_info>() - 8usize]; + ["Offset of field: vnode_info::vi_stat"][::std::mem::offset_of!(vnode_info, vi_stat) - 0usize]; + ["Offset of field: vnode_info::vi_type"] + [::std::mem::offset_of!(vnode_info, vi_type) - 136usize]; + ["Offset of field: vnode_info::vi_pad"][::std::mem::offset_of!(vnode_info, vi_pad) - 140usize]; + ["Offset of field: vnode_info::vi_fsid"] + [::std::mem::offset_of!(vnode_info, vi_fsid) - 144usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vnode_info_path { + pub vip_vi: vnode_info, + pub vip_path: [::std::os::raw::c_char; 1024usize], +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of vnode_info_path"][::std::mem::size_of::<vnode_info_path>() - 1176usize]; + ["Alignment of vnode_info_path"][::std::mem::align_of::<vnode_info_path>() - 8usize]; + ["Offset of field: vnode_info_path::vip_vi"] + [::std::mem::offset_of!(vnode_info_path, vip_vi) - 0usize]; + ["Offset of field: vnode_info_path::vip_path"] + [::std::mem::offset_of!(vnode_info_path, vip_path) - 152usize]; +}; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vnode_fdinfowithpath { + pub pfi: proc_fileinfo, + pub pvip: vnode_info_path, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of vnode_fdinfowithpath"][::std::mem::size_of::<vnode_fdinfowithpath>() - 1200usize]; + ["Alignment of vnode_fdinfowithpath"][::std::mem::align_of::<vnode_fdinfowithpath>() - 8usize]; + ["Offset of field: vnode_fdinfowithpath::pfi"] + [::std::mem::offset_of!(vnode_fdinfowithpath, pfi) - 0usize]; + ["Offset of field: vnode_fdinfowithpath::pvip"] + [::std::mem::offset_of!(vnode_fdinfowithpath, pvip) - 24usize]; +}; diff --git a/talpid-macos/src/generate-bindings.sh b/talpid-macos/src/generate-bindings.sh new file mode 100755 index 0000000000..5aac30142c --- /dev/null +++ b/talpid-macos/src/generate-bindings.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# This generates new bindings from 'proc_info.h'. +# bindgen is required: cargo install bindgen-cli + +set -eu + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$SCRIPT_DIR" + +MACOS_SDK_PATH="$(xcrun --sdk macosx --show-sdk-path)" +PROC_INFO_PATH="$MACOS_SDK_PATH/usr/include/sys/proc_info.h" + +cp ./apsl-header ./bindings.rs + +bindgen "$PROC_INFO_PATH" \ + --allowlist-item "^PROC_PIDFDVNODEPATHINFO" \ + --allowlist-item "^PROX_FDTYPE_VNODE" \ + --allowlist-item "^vnode_fdinfowithpath" \ + >> ./bindings.rs diff --git a/talpid-macos/src/lib.rs b/talpid-macos/src/lib.rs index 964dd689e0..5a282660d3 100644 --- a/talpid-macos/src/lib.rs +++ b/talpid-macos/src/lib.rs @@ -5,3 +5,7 @@ /// Processes pub mod process; + +/// OS bindings generated by 'generate_bindings.rs' +#[allow(non_camel_case_types)] +mod bindings; diff --git a/talpid-macos/src/process.rs b/talpid-macos/src/process.rs index 69722e054c..0e72d2f7ca 100644 --- a/talpid-macos/src/process.rs +++ b/talpid-macos/src/process.rs @@ -1,9 +1,16 @@ -use libc::{c_void, pid_t, proc_listallpids, proc_pidpath}; +use libc::{ + c_void, pid_t, proc_bsdinfo, proc_fdinfo, proc_listallpids, proc_pidfdinfo, proc_pidinfo, + proc_pidpath, PROC_PIDLISTFDS, PROC_PIDTBSDINFO, +}; use std::{ + ffi::{c_int, CStr, CString}, io, path::{Path, PathBuf}, + ptr, }; +use crate::bindings::{vnode_fdinfowithpath, PROC_PIDFDVNODEPATHINFO, PROX_FDTYPE_VNODE}; + /// 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() { @@ -65,3 +72,89 @@ pub fn process_path(pid: pid_t) -> io::Result<PathBuf> { .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid process path"))?, )) } + +/// Return file descriptors associated with `pid` +// reference: lsof source code: https://github.com/apple-oss-distributions/lsof/blob/c48c28f51e82a5d682a4459bdbdc42face73468f/lsof/dialects/darwin/libproc/dproc.c#L623 +pub fn process_file_descriptors(pid: pid_t) -> io::Result<Vec<proc_fdinfo>> { + // SAFETY: Passing nil arguments is safe and returns the required buffer size for the given pid + let fds_buf_size = unsafe { proc_pidinfo(pid, PROC_PIDLISTFDS, 0, ptr::null_mut(), 0) }; + + if fds_buf_size < 0 { + return Err(io::Error::last_os_error()); + } + + let fds_num = fds_buf_size as usize / std::mem::size_of::<proc_fdinfo>(); + + // SAFETY: This is a pure C struct which we're expected to zero-initialize + let empty_fdinfo = unsafe { std::mem::zeroed::<proc_fdinfo>() }; + + let mut file_desc_buf = vec![empty_fdinfo; fds_num as usize]; + + // SAFETY: fds_buf is large enough to contain `fds_num` + let fds_buf_size = unsafe { + proc_pidinfo( + pid, + PROC_PIDLISTFDS, + 0, + file_desc_buf.as_mut_ptr() as _, + fds_buf_size as c_int, + ) + }; + if fds_buf_size < 0 { + return Err(io::Error::last_os_error()); + } + + // Truncate file descriptor vector based on new count + let new_fds_num = fds_buf_size as usize / std::mem::size_of::<proc_fdinfo>(); + assert!(new_fds_num <= fds_num); + file_desc_buf.truncate(new_fds_num); + + Ok(file_desc_buf) +} + +/// Return the file path that belongs to a vnode file descriptor type for a given process. +pub fn get_file_desc_vnode_path(pid: pid_t, info: &proc_fdinfo) -> io::Result<CString> { + assert!(info.proc_fdtype == PROX_FDTYPE_VNODE as _); + + // SAFETY: This is a pure C struct which we're expected to zero-initialize + let mut vnode: vnode_fdinfowithpath = unsafe { std::mem::zeroed() }; + + // SAFETY: Our buffer is initialized, aligned, and large enough to contain the result. + let err = unsafe { + proc_pidfdinfo( + pid, + info.proc_fd, + PROC_PIDFDVNODEPATHINFO as _, + &mut vnode as *mut _ as _, + std::mem::size_of_val(&vnode) as _, + ) + }; + if err <= 0 { + return Err(io::Error::last_os_error()); + } + + // SAFETY: `proc_pidfdinfo` returned a null-terminated path here + let cstr_path = unsafe { CStr::from_ptr(vnode.pvip.vip_path.as_ptr()) }; + Ok(cstr_path.to_owned()) +} + +/// Return the 'proc_bsdinfo' associated with a given process identifier +pub fn process_bsdinfo(pid: pid_t) -> io::Result<proc_bsdinfo> { + // SAFETY: This is a pure C struct which we're expected to zero-initialize + let mut info: proc_bsdinfo = unsafe { std::mem::zeroed() }; + + // SAFETY: Our buffer (info) is initialized, aligned, and large enough to contain the result. + let err = unsafe { + proc_pidinfo( + pid, + PROC_PIDTBSDINFO as _, + 0, + &mut info as *mut proc_bsdinfo as *mut c_void, + std::mem::size_of_val(&info) as _, + ) + }; + if err <= 0 { + return Err(io::Error::last_os_error()); + } + Ok(info) +} |
