use std::{fs::OpenOptions, os::fd::AsFd};
use catalejo_sys::exception::{
Context, InvalidSlabSize, InvalidSoftSlabLimit, SlabAllocationError, SlabSize, SoftSlabLimit,
backend::Backend,
};
#[test]
fn slab_sizes_preserve_kernel_constraints() {
assert_eq!(SlabSize::new(0), Err(InvalidSlabSize::Zero));
assert_eq!(SlabSize::new(4096), Err(InvalidSlabSize::Misaligned));
assert_eq!(SlabSize::new(12 * 1024), Ok(SlabSize::DEFAULT));
assert_eq!(SlabSize::DEFAULT.record_capacity(), 256);
assert_eq!(
SlabSize::new(2 * 1024 * 1024 + 4096),
Err(InvalidSlabSize::TooLarge)
);
}
#[test]
fn soft_limits_preserve_kernel_bound() {
assert_eq!(SoftSlabLimit::new(0), Err(InvalidSoftSlabLimit::Zero));
assert_eq!(SoftSlabLimit::new(1), Ok(SoftSlabLimit::DEFAULT));
assert_eq!(
SoftSlabLimit::new(17),
Err(InvalidSoftSlabLimit::AboveKernelLimit)
);
}
fn device() -> std::fs::File {
OpenOptions::new()
.read(true)
.write(true)
.open("/dev/mirilla")
.expect("the Mirilla integration device must open")
}
#[test]
#[ignore = "requires a loaded Mirilla module"]
fn empty_slab_toggles_between_protection_states() {
let device = device();
let context =
unsafe { Context::create(device.as_fd(), SlabSize::DEFAULT, SoftSlabLimit::DEFAULT) }
.expect("the exception context must be created");
let mut target_slab = context.map().expect("the slab must map");
assert_eq!(context.allocated(), 1);
assert!(!target_slab.is_published());
target_slab.publish().expect("an empty table is valid");
assert!(target_slab.is_published());
assert_eq!(target_slab.record_list().len(), 256);
assert!(target_slab.record_list_mut().is_none());
target_slab.edit().expect("the slab must return to editing");
assert!(!target_slab.is_published());
assert!(target_slab.record_list_mut().is_some());
drop(target_slab);
assert_eq!(context.allocated(), 0);
}
#[test]
#[ignore = "requires a loaded Mirilla module"]
fn publication_error_preserves_editable_state() {
let device = device();
let context =
unsafe { Context::create(device.as_fd(), SlabSize::DEFAULT, SoftSlabLimit::DEFAULT) }
.expect("the exception context must be created");
let mut target_slab = context.map().expect("the slab must map");
target_slab
.record_list_mut()
.expect("a new slab is editable")[0]
.boundary
.base_address = 1;
let error = target_slab
.publish()
.expect_err("the incomplete record must fail validation");
assert_eq!(error.raw_os_error(), Some(libc::EINVAL));
assert!(!target_slab.is_published());
target_slab
.record_list_mut()
.expect("failed publication preserves editability")[0] =
unsafe { core::mem::zeroed() };
target_slab
.publish()
.expect("the repaired empty table is valid");
drop(target_slab);
assert_eq!(context.allocated(), 0);
}
#[test]
#[ignore = "requires a loaded Mirilla module"]
fn soft_allocation_limit_is_reusable() {
let device = device();
let context =
unsafe { Context::create(device.as_fd(), SlabSize::DEFAULT, SoftSlabLimit::DEFAULT) }
.expect("the exception context must be created");
let first = context.map().expect("the first slab must map");
assert!(matches!(
context.map(),
Err(SlabAllocationError::SoftLimitReached)
));
drop(first);
let replacement = context.map().expect("dropping returns the soft slot");
drop(replacement);
}
#[test]
#[ignore = "requires a loaded Mirilla module"]
fn sealed_slab_keeps_its_soft_allocation_slot() {
let device = device();
let child = unsafe { libc::fork() };
assert!(child >= 0, "fork must succeed");
if child == 0 {
let context = match unsafe {
Context::create(device.as_fd(), SlabSize::DEFAULT, SoftSlabLimit::DEFAULT)
} {
Ok(context) => context,
Err(_) => unsafe { libc::_exit(2) },
};
let mut target_slab = match context.map() {
Ok(target_slab) => target_slab,
Err(_) => unsafe { libc::_exit(3) },
};
if target_slab.publish().is_err() {
unsafe { libc::_exit(4) };
}
let record_pointer = target_slab
.record_list()
.as_ptr()
.cast::<core::ffi::c_void>();
let slab_size = SlabSize::DEFAULT.get();
let seal_status = unsafe { libc::syscall(libc::SYS_mseal, record_pointer, slab_size, 0) };
if seal_status < 0 {
let seal_error = std::io::Error::last_os_error();
match seal_error.raw_os_error() {
Some(libc::ENOSYS) => unsafe { libc::_exit(0) },
_ => unsafe { libc::_exit(5) },
}
}
drop(target_slab);
if context.allocated() != 1 {
unsafe { libc::_exit(6) };
}
unsafe { libc::_exit(0) };
}
let mut child_status = 0;
assert_eq!(
unsafe { libc::waitpid(child, &raw mut child_status, 0) },
child
);
assert!(libc::WIFEXITED(child_status));
assert_eq!(libc::WEXITSTATUS(child_status), 0);
}
#[test]
#[ignore = "requires a loaded Mirilla module"]
fn zz_backend_reuse_and_postfork_rebuild() {
let device = device();
let left = unsafe { Backend::initialize(device.as_fd()) }.expect("backend initialization");
let right = Backend::retrieve().expect("backend retrieval");
assert_eq!(left, right);
let child = unsafe { libc::fork() };
assert!(child >= 0, "fork must succeed");
if child == 0 {
let inherited = left
.validate()
.expect_err("the inherited proof must be stale");
if inherited.raw_os_error() != Some(libc::ESTALE) {
unsafe { libc::_exit(2) };
}
let stale = Backend::retrieve().expect_err("the inherited backend must be stale");
if stale.raw_os_error() != Some(libc::ESTALE) {
unsafe { libc::_exit(3) };
}
let child_backend = match unsafe { Backend::initialize(device.as_fd()) } {
Ok(child_backend) => child_backend,
Err(_) => {
unsafe { libc::_exit(4) };
}
};
if child_backend.validate().is_err() {
unsafe { libc::_exit(5) };
}
unsafe { libc::_exit(0) };
}
let mut child_status = 0;
assert_eq!(
unsafe { libc::waitpid(child, &raw mut child_status, 0) },
child
);
assert!(libc::WIFEXITED(child_status));
assert_eq!(libc::WEXITSTATUS(child_status), 0);
}