#![allow(
dead_code,
reason = "private role-scoped evidence remains unreachable until native session composition"
)]
use crate::negotiation::AcceptedTranscriptFacts;
use crate::protocol::{CapabilityFrame, NativeAuthorityProfile};
use crate::session::{AbsoluteDeadline, AtomicCapabilities, ProtocolVersion, SessionLimits};
use core::cell::Cell;
use core::marker::PhantomData;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SpawnIdentityFacts {
parent_pid: u32,
child_pid: u32,
parent_uid: u32,
parent_gid: u32,
child_uid: u32,
child_gid: u32,
nonce: [u8; 32],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct AcceptedSessionParameters {
facts: SpawnIdentityFacts,
limits: SessionLimits,
authority_profile: NativeAuthorityProfile,
atomics: AtomicCapabilities,
protocol_version: ProtocolVersion,
}
impl AcceptedSessionParameters {
pub(crate) const fn facts(self) -> SpawnIdentityFacts {
self.facts
}
pub(crate) const fn limits(self) -> SessionLimits {
self.limits
}
pub(crate) const fn authority_profile(self) -> NativeAuthorityProfile {
self.authority_profile
}
pub(crate) const fn atomics(self) -> AtomicCapabilities {
self.atomics
}
pub(crate) const fn protocol_version(self) -> ProtocolVersion {
self.protocol_version
}
}
impl SpawnIdentityFacts {
fn new(
parent_pid: u32,
child_pid: u32,
parent_uid: u32,
parent_gid: u32,
child_uid: u32,
child_gid: u32,
nonce: [u8; 32],
) -> Option<Self> {
if parent_pid == 0 || child_pid == 0 || parent_pid == child_pid || nonce == [0; 32] {
return None;
}
Some(Self {
parent_pid,
child_pid,
parent_uid,
parent_gid,
child_uid,
child_gid,
nonce,
})
}
pub(crate) const fn parent_pid(self) -> u32 {
self.parent_pid
}
pub(crate) const fn child_pid(self) -> u32 {
self.child_pid
}
pub(crate) const fn parent_uid(self) -> u32 {
self.parent_uid
}
pub(crate) const fn parent_gid(self) -> u32 {
self.parent_gid
}
pub(crate) const fn child_uid(self) -> u32 {
self.child_uid
}
pub(crate) const fn child_gid(self) -> u32 {
self.child_gid
}
pub(crate) const fn nonce(self) -> [u8; 32] {
self.nonce
}
}
pub(crate) struct CoordinatorChildChannelReceipt {
facts: SpawnIdentityFacts,
}
impl CoordinatorChildChannelReceipt {
unsafe fn from_verified_native(facts: SpawnIdentityFacts) -> Self {
Self { facts }
}
}
pub(crate) struct CoordinatorChildImageReceipt {
facts: SpawnIdentityFacts,
}
impl CoordinatorChildImageReceipt {
unsafe fn from_verified_native(facts: SpawnIdentityFacts) -> Self {
Self { facts }
}
}
pub(crate) struct CoordinatorAcceptedEvidence {
facts: SpawnIdentityFacts,
transcript: AcceptedTranscriptFacts,
not_sync: PhantomData<Cell<()>>,
}
impl CoordinatorAcceptedEvidence {
fn combine(
channel: CoordinatorChildChannelReceipt,
image: CoordinatorChildImageReceipt,
transcript: AcceptedTranscriptFacts,
) -> Result<Self, SessionTransportError> {
if channel.facts != image.facts || channel.facts.nonce != transcript.nonce() {
return Err(SessionTransportError::IdentityMismatch);
}
Ok(Self {
facts: channel.facts,
transcript,
not_sync: PhantomData,
})
}
pub(crate) const fn facts(&self) -> SpawnIdentityFacts {
self.facts
}
pub(crate) const fn session_parameters(
&self,
authority_profile: NativeAuthorityProfile,
) -> AcceptedSessionParameters {
let (major, minor) = self.transcript.wire_version();
AcceptedSessionParameters {
facts: self.facts,
limits: self.transcript.effective_limits(),
authority_profile,
atomics: AtomicCapabilities::from_accepted_offer(self.transcript.effective_atomics()),
protocol_version: ProtocolVersion::new(major, minor),
}
}
}
pub(crate) struct ReceiverSpawnerEvidence {
facts: SpawnIdentityFacts,
transcript: AcceptedTranscriptFacts,
not_sync: PhantomData<Cell<()>>,
}
impl ReceiverSpawnerEvidence {
unsafe fn from_verified_native(
facts: SpawnIdentityFacts,
transcript: AcceptedTranscriptFacts,
) -> Result<Self, SessionTransportError> {
if facts.nonce != transcript.nonce() {
return Err(SessionTransportError::IdentityMismatch);
}
Ok(Self {
facts,
transcript,
not_sync: PhantomData,
})
}
pub(crate) const fn facts(&self) -> SpawnIdentityFacts {
self.facts
}
pub(crate) const fn session_parameters(
&self,
authority_profile: NativeAuthorityProfile,
) -> AcceptedSessionParameters {
let (major, minor) = self.transcript.wire_version();
AcceptedSessionParameters {
facts: self.facts,
limits: self.transcript.effective_limits(),
authority_profile,
atomics: AtomicCapabilities::from_accepted_offer(self.transcript.effective_atomics()),
protocol_version: ProtocolVersion::new(major, minor),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PeerState {
Running,
ExitedUnknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SessionTransportError {
DeadlineExpired,
PeerExited,
MalformedRecord,
RecordTooLarge,
IdentityMismatch,
Ambiguous,
Poisoned,
Native(Option<i32>),
}
pub(crate) mod sealed {
pub(crate) trait Sealed {}
}
pub(crate) trait AuthenticatedZeroRightsTransport: sealed::Sealed {
fn send_record(
&mut self,
bytes: &[u8],
deadline: AbsoluteDeadline,
) -> Result<(), SessionTransportError>;
fn receive_record(
&mut self,
maximum: usize,
deadline: AbsoluteDeadline,
) -> Result<Vec<u8>, SessionTransportError>;
fn try_poll_peer(&mut self) -> Result<PeerState, SessionTransportError>;
fn poison(&mut self);
}
pub(crate) trait CoordinatorCapabilityTransport: AuthenticatedZeroRightsTransport {
type Capabilities<'a>
where
Self: 'a;
fn send_capability_record(
&mut self,
frame: &CapabilityFrame,
capabilities: Self::Capabilities<'_>,
deadline: AbsoluteDeadline,
) -> Result<(), SessionTransportError>;
}
pub(crate) trait ReceiverCapabilityTransport: AuthenticatedZeroRightsTransport {
type ReceivedCapabilities;
fn receive_capability_record(
&mut self,
expected: &CapabilityFrame,
deadline: AbsoluteDeadline,
) -> Result<Self::ReceivedCapabilities, SessionTransportError>;
}
pub(crate) trait OwnedChildLifecycle: sealed::Sealed {
fn terminate_and_reap(
&mut self,
deadline: AbsoluteDeadline,
) -> Result<(), SessionTransportError>;
}
mod accepted_control;
mod reaper_ownership;
#[cfg(target_os = "linux")]
#[deny(dead_code)]
pub(crate) mod linux;
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub(crate) mod linux_vnext;
#[cfg(target_os = "macos")]
#[allow(dead_code)]
pub(crate) mod macos;
#[cfg(target_os = "windows")]
#[allow(dead_code)]
pub(crate) mod windows;
#[cfg(target_os = "linux")]
pub(crate) fn mint_incarnation() -> Result<[u8; 16], ()> {
let mut bytes = [0_u8; 16];
let mut filled = 0;
while filled < bytes.len() {
let result = unsafe {
libc::getrandom(bytes[filled..].as_mut_ptr().cast(), bytes.len() - filled, 0)
};
if result < 0 {
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
return Err(());
}
if result == 0 {
return Err(());
}
filled += usize::try_from(result).map_err(|_| ())?;
}
(bytes != [0; 16]).then_some(bytes).ok_or(())
}
#[cfg(target_os = "macos")]
pub(crate) fn mint_incarnation() -> Result<[u8; 16], ()> {
unsafe extern "C" {
fn arc4random_buf(buffer: *mut core::ffi::c_void, length: usize);
}
let mut bytes = [0_u8; 16];
unsafe { arc4random_buf(bytes.as_mut_ptr().cast(), bytes.len()) };
(bytes != [0; 16]).then_some(bytes).ok_or(())
}
#[cfg(target_os = "windows")]
pub(crate) fn mint_incarnation() -> Result<[u8; 16], ()> {
use windows_sys::Win32::Security::Cryptography::{
BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
};
let mut bytes = [0_u8; 16];
let status = unsafe {
BCryptGenRandom(
core::ptr::null_mut(),
bytes.as_mut_ptr(),
bytes.len() as u32,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
};
if status != 0 || bytes == [0; 16] {
return Err(());
}
Ok(bytes)
}
#[cfg(test)]
#[path = "mod_test.rs"]
mod receipt_tests;
#[cfg(test)]
#[path = "accepted_control_test.rs"]
mod accepted_control_tests;
#[cfg(all(test, loom))]
#[path = "reaper_ownership_test.rs"]
mod reaper_ownership_tests;