use super::vnext_memory::{MacBatchError, MacExpectedMixedDirectionBatch, MacMixedDirectionBatch};
use super::{
LocalWriterRegion, QuiescentRegion, VM_PROT_EXECUTE, VM_PROT_READ, VM_PROT_WRITE, bootstrap,
current_task, mach_vm_protect, page_size, set_vnext_drop_observer_for_test,
};
use crate::batch::{ExpectedBatch, ExpectedRegion, LocalRegionAuthority, TransferBatch};
use crate::protocol::{NativeAuthorityProfile, TransferManifest};
use crate::region::{PrivateRegion, RegionId, RegionOptions, RegionSpec, WriterEndpoint};
use crate::session::{AbsoluteDeadline, SessionLimits};
use std::sync::{Arc, Mutex};
use std::time::Duration;
fn deadline() -> AbsoluteDeadline {
AbsoluteDeadline::after(Duration::from_secs(10)).unwrap()
}
fn build_batch(count: usize) -> (TransferBatch, ExpectedBatch) {
let mut batch = TransferBatch::new(16, 1 << 20, 16 << 20).unwrap();
let mut expected = Vec::with_capacity(count);
for index in (0..count).rev() {
let id = RegionId::new((index + 1) as u128).unwrap();
let writer = if index % 2 == 0 {
WriterEndpoint::Coordinator
} else {
WriterEndpoint::Receiver
};
let logical_len = 31 + index;
let mut region = PrivateRegion::allocate(RegionOptions::fixed(logical_len)).unwrap();
region.initialize(|bytes| {
bytes.fill(0);
bytes[0] = (index + 1) as u8;
});
batch
.add(region.prepare(RegionSpec { id, writer }).unwrap())
.unwrap();
expected.push(ExpectedRegion::new(id, writer, logical_len));
}
(batch, ExpectedBatch::try_from_regions(expected).unwrap())
}
fn manifest(entries: Vec<crate::protocol::ManifestEntry>) -> TransferManifest {
TransferManifest::new_with_authority(
[7; 32],
std::process::id(),
std::process::id().checked_add(1).unwrap(),
1,
NativeAuthorityProfile::MacMachV1,
entries,
)
.unwrap()
}
fn run_full_cycle(count: usize) {
let (batch, expected) = build_batch(count);
let prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let rights = prepared.copied_capabilities_for_test().unwrap();
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
let imported = expected.import(&transfer, rights).unwrap();
drop(imported.into_active_region_owners());
drop(prepared);
}
#[test]
fn native_owner_lifecycle_has_no_leak_across_ten_thousand_cycles() {
const COUNT: usize = 2;
const CYCLES: usize = 10_000;
let calibration = Arc::new(Mutex::new(Vec::new()));
set_vnext_drop_observer_for_test(Some(calibration.clone()));
run_full_cycle(COUNT);
set_vnext_drop_observer_for_test(None);
let per_cycle = calibration.lock().unwrap().len();
assert!(per_cycle > 0, "a full cycle must drop native owners");
let drops = Arc::new(Mutex::new(Vec::new()));
set_vnext_drop_observer_for_test(Some(drops.clone()));
for _ in 0..CYCLES {
run_full_cycle(COUNT);
}
set_vnext_drop_observer_for_test(None);
assert_eq!(
drops.lock().unwrap().len(),
per_cycle * CYCLES,
"every native owner released across {CYCLES} cycles with no leak"
);
}
#[test]
fn native_owner_drop_order_is_leak_free_in_both_permutations() {
fn cycle_drops(drop_imported_first: bool) -> usize {
let (batch, expected) = build_batch(4);
let prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let rights = prepared.copied_capabilities_for_test().unwrap();
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
let owners = expected
.import(&transfer, rights)
.unwrap()
.into_active_region_owners();
let drops = Arc::new(Mutex::new(Vec::new()));
set_vnext_drop_observer_for_test(Some(drops.clone()));
if drop_imported_first {
drop(owners);
drop(prepared);
} else {
drop(prepared);
drop(owners);
}
set_vnext_drop_observer_for_test(None);
drops.lock().unwrap().len()
}
let imported_first = cycle_drops(true);
let prepared_first = cycle_drops(false);
assert!(
imported_first > 0,
"dropping the owners must release natives"
);
assert_eq!(
imported_first, prepared_first,
"both endpoints release the same native owners regardless of drop order"
);
}
#[test]
fn mixed_batches_prepare_import_and_activate_complementary_authority() {
for count in [1, 2, 4, 16] {
let (batch, expected) = build_batch(count);
let mut prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let rights = prepared.copied_capabilities_for_test().unwrap();
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
assert!(expected.matches_manifest(&transfer));
let mut imported = expected.import(&transfer, rights).unwrap();
for ordinal in 0..count {
if ordinal % 2 == 0 {
assert_eq!(
imported.read_coordinator_for_test(ordinal, 0),
(ordinal + 1) as u8
);
prepared.write_coordinator_for_test(ordinal, 1, 0x40 + ordinal as u8);
assert_eq!(
imported.read_coordinator_for_test(ordinal, 1),
0x40 + ordinal as u8
);
} else {
imported.write_receiver_for_test(ordinal, 1, 0x60 + ordinal as u8);
assert_eq!(
prepared.read_receiver_for_test(ordinal, 1),
0x60 + ordinal as u8
);
}
}
let local_specs = prepared.activation_specs().unwrap();
let peer_specs = imported.activation_specs(deadline()).unwrap();
assert_eq!(local_specs.len(), count);
assert_eq!(peer_specs.len(), count);
for (ordinal, (local, peer)) in local_specs.iter().zip(&peer_specs).enumerate() {
assert_eq!(local.id, peer.id);
assert_eq!(local.logical_len, peer.logical_len);
assert_eq!(local.mapped_len, peer.mapped_len);
assert_eq!(
(local.authority, peer.authority),
if ordinal % 2 == 0 {
(LocalRegionAuthority::Writer, LocalRegionAuthority::Reader)
} else {
(LocalRegionAuthority::Reader, LocalRegionAuthority::Writer)
}
);
}
let local_owners = prepared.into_active_region_owners();
let peer_owners = imported.into_active_region_owners();
assert_eq!(local_owners.len(), count);
assert_eq!(peer_owners.len(), count);
for (ordinal, (local, peer)) in local_owners.into_iter().zip(peer_owners).enumerate() {
if ordinal % 2 == 0 {
let mut writer = local.into_writer().expect("coordinator owns writer");
let reader = peer.into_reader().expect("receiver owns reader");
unsafe { core::ptr::write_volatile(writer.as_mut_ptr().add(2), 0x80) };
assert_eq!(
unsafe { core::ptr::read_volatile(reader.as_ptr().add(2)) },
0x80
);
} else {
let reader = local.into_reader().expect("coordinator owns reader");
let mut writer = peer.into_writer().expect("receiver owns writer");
unsafe { core::ptr::write_volatile(writer.as_mut_ptr().add(2), 0x90) };
assert_eq!(
unsafe { core::ptr::read_volatile(reader.as_ptr().add(2)) },
0x90
);
}
}
}
}
#[test]
fn preparation_and_import_failures_drop_partial_native_owners() {
for count in [1, 2, 4, 16] {
for failure_at in 1..=count {
let (batch, _) = build_batch(count);
let drops = Arc::new(Mutex::new(Vec::new()));
set_vnext_drop_observer_for_test(Some(drops.clone()));
assert_eq!(
MacMixedDirectionBatch::prepare_with_failure_for_test(
batch,
failure_at,
deadline()
)
.err()
.unwrap(),
MacBatchError::WrongObject
);
set_vnext_drop_observer_for_test(None);
let drops = drops.lock().unwrap();
assert_eq!(
drops.iter().filter(|event| **event == "mapping").count(),
count
);
assert_eq!(
drops
.iter()
.filter(|event| **event == "memory-entry")
.count(),
failure_at
);
}
for failure_at in 1..=count {
let (batch, expected) = build_batch(count);
let prepared = MacMixedDirectionBatch::prepare(
batch,
NativeAuthorityProfile::MacMachV1,
deadline(),
)
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let rights = prepared.copied_capabilities_for_test().unwrap();
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
let drops = Arc::new(Mutex::new(Vec::new()));
set_vnext_drop_observer_for_test(Some(drops.clone()));
assert_eq!(
expected
.import_with_failure_for_test(&transfer, rights, failure_at)
.err()
.unwrap(),
MacBatchError::WrongObject
);
set_vnext_drop_observer_for_test(None);
let drops = drops.lock().unwrap();
assert_eq!(
drops.iter().filter(|event| **event == "mapping").count(),
failure_at
);
assert_eq!(
drops.iter().filter(|event| **event == "send-right").count(),
count
);
drop(drops);
prepared.revalidate_before_send().unwrap();
}
}
}
#[test]
fn duplicate_right_and_wrong_profile_fail_closed() {
let (batch, expected) = build_batch(2);
let prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let first = prepared.capability_names()[0];
let rights = vec![
bootstrap::SendRight::copy_existing(first).unwrap(),
bootstrap::SendRight::copy_existing(first).unwrap(),
];
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
assert_eq!(
expected.import(&transfer, rights).err().unwrap(),
MacBatchError::WrongObject
);
let (batch, _) = build_batch(1);
assert_eq!(
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::LinuxMdweV1, deadline())
.err()
.unwrap(),
MacBatchError::WrongProvenance
);
}
#[test]
fn expectation_rejects_active_region_and_byte_limits_before_import() {
let (_, expected) = build_batch(2);
let limits = SessionLimits {
max_active_regions: 1,
..SessionLimits::default()
};
assert_eq!(
MacExpectedMixedDirectionBatch::new(expected, limits, deadline())
.err()
.unwrap(),
MacBatchError::InvalidBatch
);
let (_, expected) = build_batch(1);
let limits = SessionLimits {
max_active_bytes: u64::try_from(page_size().unwrap() - 1).unwrap(),
..SessionLimits::default()
};
assert_eq!(
MacExpectedMixedDirectionBatch::new(expected, limits, deadline())
.err()
.unwrap(),
MacBatchError::InvalidSize
);
}
#[test]
fn oversized_and_excess_right_capabilities_are_rejected() {
let (batch, expected) = build_batch(1);
let prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let page = page_size().unwrap();
let LocalWriterRegion {
peer_entry: oversized,
..
} = QuiescentRegion::new(page + 1)
.unwrap()
.into_local_writer(page * 2)
.unwrap();
let oversized = vec![bootstrap::SendRight::copy_existing(oversized.name).unwrap()];
let expected_batch = MacExpectedMixedDirectionBatch::new(
ExpectedBatch::try_from_regions(vec![ExpectedRegion::new(
RegionId::new(1).unwrap(),
WriterEndpoint::Coordinator,
31,
)])
.unwrap(),
SessionLimits::default(),
deadline(),
)
.unwrap();
assert_eq!(
expected_batch.import(&transfer, oversized).err().unwrap(),
MacBatchError::WrongObject
);
let (strong_batch, _) = build_batch(2);
let strong = MacMixedDirectionBatch::prepare(
strong_batch,
NativeAuthorityProfile::MacMachV1,
deadline(),
)
.unwrap();
let excessive =
vec![bootstrap::SendRight::copy_existing(strong.capability_names()[1]).unwrap()];
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
assert_eq!(
expected.import(&transfer, excessive).err().unwrap(),
MacBatchError::WrongObject
);
}
#[test]
fn imported_maximum_protections_exclude_execute() {
let (batch, expected) = build_batch(2);
let prepared =
MacMixedDirectionBatch::prepare(batch, NativeAuthorityProfile::MacMachV1, deadline())
.unwrap();
let transfer = manifest(prepared.manifest_entries());
let rights = prepared.copied_capabilities_for_test().unwrap();
let expected =
MacExpectedMixedDirectionBatch::new(expected, SessionLimits::default(), deadline())
.unwrap();
let imported = expected.import(&transfer, rights).unwrap();
for owner in imported.into_active_region_owners() {
let spec = owner.spec();
match spec.authority {
LocalRegionAuthority::Reader => {
let owner = owner.into_reader().unwrap();
let address = owner.as_ptr() as u64;
let write_result = unsafe {
mach_vm_protect(
current_task(),
address,
spec.mapped_len as u64,
0,
VM_PROT_READ | VM_PROT_WRITE,
)
};
assert_ne!(write_result, super::KERN_SUCCESS);
let result = unsafe {
mach_vm_protect(
current_task(),
address,
spec.mapped_len as u64,
0,
VM_PROT_READ | VM_PROT_EXECUTE,
)
};
assert_ne!(result, super::KERN_SUCCESS);
}
LocalRegionAuthority::Writer => {
let mut owner = owner.into_writer().unwrap();
let address = owner.as_mut_ptr() as u64;
let result = unsafe {
mach_vm_protect(
current_task(),
address,
spec.mapped_len as u64,
0,
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE,
)
};
assert_ne!(result, super::KERN_SUCCESS);
}
}
}
}