use std::net::SocketAddr;
use crate::peer_pid;
const MAX_ANCESTRY_HOPS: usize = 32;
#[must_use]
pub fn peer_is_launched_harness(peer: SocketAddr, launched: Option<i32>) -> bool {
let Some(launched) = launched else {
return false;
};
let Some(peer_pid) = peer_pid::lookup_owner(peer).pid else {
return false;
};
is_launched_or_descendant(peer_pid, launched)
}
#[must_use]
pub async fn peer_is_launched_harness_async(peer: SocketAddr, launched: Option<i32>) -> bool {
let Some(launched) = launched else {
return false;
};
let Some(peer_pid) = peer_pid::lookup_owner_async(peer).await.pid else {
return false;
};
is_launched_or_descendant(peer_pid, launched)
}
#[must_use]
pub fn is_launched_or_descendant(pid: i32, launched: i32) -> bool {
if launched <= 1 {
return false;
}
let mut current = pid;
for _ in 0..MAX_ANCESTRY_HOPS {
if current == launched {
return true;
}
match parent_of(current) {
Some(parent) if parent > 0 && parent != current => current = parent,
_ => return false,
}
}
false
}
#[cfg(target_os = "linux")]
fn parent_of(pid: i32) -> Option<i32> {
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
let after_comm = stat.rsplit_once(')')?.1;
after_comm.split_whitespace().nth(1)?.parse().ok()
}
#[cfg(target_os = "macos")]
fn parent_of(pid: i32) -> Option<i32> {
use std::mem::{MaybeUninit, size_of};
const PROC_PIDTBSDINFO: libc::c_int = 3;
let mut info = MaybeUninit::<libc::proc_bsdinfo>::zeroed();
let size = i32::try_from(size_of::<libc::proc_bsdinfo>()).ok()?;
let written =
unsafe { libc::proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, info.as_mut_ptr().cast(), size) };
if written != size {
return None;
}
let info = unsafe { info.assume_init() };
i32::try_from(info.pbi_ppid).ok()
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn parent_of(_pid: i32) -> Option<i32> {
None
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
struct Child(std::process::Child);
impl Child {
fn spawn() -> Self {
Self(
std::process::Command::new("sleep")
.arg("30")
.spawn()
.expect("sleep is available"),
)
}
fn pid(&self) -> i32 {
i32::try_from(self.0.id()).unwrap()
}
}
impl Drop for Child {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
fn me() -> i32 {
i32::try_from(std::process::id()).unwrap()
}
#[test]
fn a_process_is_its_own_launched_harness() {
assert!(is_launched_or_descendant(me(), me()));
}
#[test]
fn a_child_of_the_launched_process_is_trusted() {
let child = Child::spawn();
assert!(
is_launched_or_descendant(child.pid(), me()),
"a real child of this process was not recognised as its descendant",
);
}
#[test]
fn a_process_that_is_not_below_the_launched_one_is_refused() {
let child = Child::spawn();
assert!(
!is_launched_or_descendant(me(), child.pid()),
"a process outside the launched harness's subtree was trusted",
);
}
#[test]
fn nothing_is_trusted_before_the_harness_is_spawned() {
let peer = "127.0.0.1:9".parse().unwrap();
assert!(!peer_is_launched_harness(peer, None));
}
#[test]
fn pid_one_vouches_for_nobody() {
assert!(!is_launched_or_descendant(me(), 1));
assert!(!is_launched_or_descendant(me(), 0));
}
#[test]
fn an_unowned_peer_address_is_refused() {
let peer = "127.0.0.1:9".parse().unwrap();
assert!(!peer_is_launched_harness(peer, Some(me())));
}
#[tokio::test(start_paused = true)]
async fn the_async_variant_refuses_the_same_peers() {
let peer: std::net::SocketAddr = "127.0.0.1:9".parse().unwrap();
assert!(!peer_is_launched_harness_async(peer, None).await);
assert!(!peer_is_launched_harness_async(peer, Some(me())).await);
}
}