use std::os::fd::{AsFd, OwnedFd};
use std::ptr::NonNull;
#[cfg(target_os = "macos")]
use std::sync::atomic::AtomicU32;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering};
use super::{IMemoryHeap, Region, SharedBytes, FLAG_READ_ONLY, WORD};
use crate::error::{Result, StatusCode};
use crate::file_descriptor::ParcelFileDescriptor;
pub const FLAG_DONT_MAP_LOCALLY: u32 = 0x0000_0100;
pub const FLAG_NO_CACHING: u32 = 0x0000_0200;
pub const FLAG_FORCE_MEMFD: u32 = 0x0000_0400;
pub const FLAG_MEMFD_ALLOW_SEALING: u32 = 0x0000_0800;
pub const SEAL_SEAL: u32 = 0x0001;
pub const SEAL_SHRINK: u32 = 0x0002;
pub const SEAL_GROW: u32 = 0x0004;
pub const SEAL_WRITE: u32 = 0x0008;
pub const SEAL_FUTURE_WRITE: u32 = 0x0010;
#[cfg(target_os = "macos")]
static HEAP_COUNTER: AtomicU32 = AtomicU32::new(0);
fn page_size() -> usize {
rustix::param::page_size()
}
fn round_up_to_page(size: usize) -> Option<usize> {
let ps = page_size();
size.checked_add(ps - 1).map(|s| s & !(ps - 1))
}
struct Mapping {
ptr: NonNull<u8>,
len: usize,
}
impl Mapping {
fn map(fd: &OwnedFd, len: usize, offset: usize, writable: bool) -> Result<Self> {
use rustix::mm::{MapFlags, ProtFlags};
if len == 0 {
return Err(StatusCode::BadValue);
}
let mut prot = ProtFlags::READ;
if writable {
prot |= ProtFlags::WRITE;
}
let raw = unsafe {
rustix::mm::mmap(
std::ptr::null_mut(),
len,
prot,
MapFlags::SHARED,
fd,
offset as u64,
)
}?;
let ptr = NonNull::new(raw.cast::<u8>()).ok_or(StatusCode::NoMemory)?;
Ok(Self { ptr, len })
}
}
impl Mapping {
fn region(&self) -> Region<'_> {
let words = self.len / WORD;
let base = self.ptr.as_ptr();
let (words, tail) = unsafe {
(
std::slice::from_raw_parts(base.cast::<AtomicUsize>(), words),
std::slice::from_raw_parts(
base.add(words * WORD).cast::<AtomicU8>(),
self.len - words * WORD,
),
)
};
Region::new(words, tail)
}
}
impl Drop for Mapping {
fn drop(&mut self) {
let _ = unsafe { rustix::mm::munmap(self.ptr.as_ptr().cast(), self.len) };
}
}
struct HeapInner {
fd: OwnedFd,
ro_fd: Option<OwnedFd>,
export_ro: AtomicBool,
map: Option<Mapping>,
size: usize,
offset: usize,
flags: u32,
writable: bool,
}
unsafe impl Send for HeapInner {}
unsafe impl Sync for HeapInner {}
impl HeapInner {
fn region(&self) -> Result<Region<'_>> {
self.map
.as_ref()
.map(Mapping::region)
.ok_or(StatusCode::InvalidOperation)
}
fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<()> {
self.region()?.load(off, dst)
}
fn write_at(&self, off: usize, src: &[u8]) -> Result<()> {
if !self.writable {
return Err(StatusCode::PermissionDenied);
}
self.region()?.store(off, src)
}
fn base(&self) -> Option<SharedBytes<'_>> {
self.region().ok().map(SharedBytes::whole)
}
fn as_ptr(&self) -> Option<*mut u8> {
self.map.as_ref().map(|m| m.ptr.as_ptr())
}
fn export_fd(&self) -> &OwnedFd {
match &self.ro_fd {
Some(ro) if self.export_ro.load(Ordering::Relaxed) => ro,
_ => &self.fd,
}
}
fn to_parcel_fd(&self) -> Result<ParcelFileDescriptor> {
Ok(ParcelFileDescriptor::new(self.export_fd().try_clone()?))
}
fn seals(&self) -> Option<u32> {
backend::get_seals(self.export_fd())
}
fn seal_future_write(&self) -> Result<()> {
backend::seal_future_write(&self.fd, self.ro_fd.as_ref()).map_err(|e| {
if e == StatusCode::PermissionDenied {
StatusCode::InvalidOperation
} else {
e
}
})?;
self.export_ro.store(true, Ordering::Relaxed);
Ok(())
}
fn impl_heap_id(&self) -> i32 {
use std::os::fd::AsRawFd;
self.export_fd().as_raw_fd()
}
}
struct Created {
fd: OwnedFd,
ro_fd: Option<OwnedFd>,
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod backend {
use super::*;
use rustix::fs::{MemfdFlags, SealFlags};
pub(super) fn create(name: &str, size: usize) -> Result<Created> {
let fd = rustix::fs::memfd_create(name, MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING)?;
rustix::fs::ftruncate(&fd, size as u64)?;
Ok(Created { fd, ro_fd: None })
}
pub(super) fn seal_future_write(fd: &OwnedFd, _ro: Option<&OwnedFd>) -> Result<()> {
rustix::fs::fcntl_add_seals(fd, SealFlags::FUTURE_WRITE)?;
Ok(())
}
pub(super) fn seal(fd: &OwnedFd, flags: u32) -> Result<()> {
let mut seals = SealFlags::GROW | SealFlags::SHRINK;
if flags & FLAG_READ_ONLY != 0 {
seals |= SealFlags::FUTURE_WRITE;
}
if flags & FLAG_MEMFD_ALLOW_SEALING == 0 {
seals |= SealFlags::SEAL;
}
match rustix::fs::fcntl_add_seals(fd, seals) {
Ok(()) => Ok(()),
Err(rustix::io::Errno::INVAL) if seals.contains(SealFlags::FUTURE_WRITE) => {
log::warn!("memfd F_SEAL_FUTURE_WRITE unsupported by this kernel; read-only heap is not write-sealed");
rustix::fs::fcntl_add_seals(fd, seals - SealFlags::FUTURE_WRITE)?;
Ok(())
}
Err(e) => Err(e.into()),
}
}
pub(super) fn get_seals(fd: &OwnedFd) -> Option<u32> {
rustix::fs::fcntl_get_seals(fd).ok().map(|s| s.bits())
}
}
#[cfg(target_os = "macos")]
mod backend {
use super::*;
use rustix::fs::Mode;
use rustix::shm::OFlags;
pub(super) fn create(name: &str, size: usize) -> Result<Created> {
let pid = rustix::process::getpid().as_raw_nonzero().get();
let tag: String = name.chars().filter(char::is_ascii_alphanumeric).collect();
let mut last = StatusCode::Unknown;
for _ in 0..3 {
let ctr = HEAP_COUNTER.fetch_add(1, Ordering::Relaxed);
let mut shm_name = format!("/rsb{pid}-{ctr}-{tag}");
shm_name.truncate(31);
let fd = match rustix::shm::open(
shm_name.as_str(),
OFlags::CREATE | OFlags::EXCL | OFlags::RDWR,
Mode::RUSR | Mode::WUSR,
) {
Ok(fd) => fd,
Err(rustix::io::Errno::EXIST) => {
last = StatusCode::AlreadyExists;
continue;
}
Err(e) => return Err(e.into()),
};
let ro = rustix::shm::open(shm_name.as_str(), OFlags::RDONLY, Mode::empty());
let _ = rustix::shm::unlink(shm_name.as_str());
let ro = ro?;
rustix::fs::ftruncate(&fd, size as u64)?;
return Ok(Created {
fd,
ro_fd: Some(ro),
});
}
Err(last)
}
pub(super) fn seal(_fd: &OwnedFd, _flags: u32) -> Result<()> {
Ok(())
}
pub(super) fn seal_future_write(fd: &OwnedFd, ro: Option<&OwnedFd>) -> Result<()> {
if ro.is_some() || is_read_only_fd(fd) {
Ok(())
} else {
Err(StatusCode::InvalidOperation)
}
}
pub(super) fn is_read_only_fd<F: AsFd>(fd: F) -> bool {
use rustix::fs::OFlags;
rustix::fs::fcntl_getfl(fd)
.map(|fl| fl & OFlags::ACCMODE == OFlags::RDONLY)
.unwrap_or(false)
}
pub(super) fn get_seals(fd: &OwnedFd) -> Option<u32> {
let st = rustix::fs::fstat(fd).ok()?;
if u32::from(st.st_mode) & u32::from(libc::S_IFMT) != 0 {
return None;
}
let mut seals = SEAL_SHRINK | SEAL_GROW;
if is_read_only_fd(fd) {
seals |= SEAL_FUTURE_WRITE;
}
Some(seals)
}
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
mod backend {
use super::*;
pub(super) fn create(_name: &str, _size: usize) -> Result<Created> {
Err(StatusCode::InvalidOperation)
}
pub(super) fn seal(_fd: &OwnedFd, _flags: u32) -> Result<()> {
Err(StatusCode::InvalidOperation)
}
pub(super) fn seal_future_write(_fd: &OwnedFd, _ro: Option<&OwnedFd>) -> Result<()> {
Err(StatusCode::InvalidOperation)
}
pub(super) fn get_seals(_fd: &OwnedFd) -> Option<u32> {
None
}
}
#[cfg(target_os = "macos")]
pub(super) fn is_read_only_fd<F: AsFd>(fd: F) -> bool {
backend::is_read_only_fd(fd)
}
pub const fn is_supported() -> bool {
cfg!(any(
target_os = "linux",
target_os = "android",
target_os = "macos"
))
}
pub struct MemoryHeapBase(HeapInner);
impl std::fmt::Debug for MemoryHeapBase {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryHeapBase")
.field("fd", &self.0.impl_heap_id())
.field("size", &self.0.size)
.field("flags", &format_args!("{:#x}", self.0.flags))
.field("mapped", &self.0.map.is_some())
.finish()
}
}
impl MemoryHeapBase {
pub fn new(size: usize, flags: u32) -> Result<Self> {
Self::new_named(size, flags, "MemoryHeapBase")
}
pub fn new_named(size: usize, flags: u32, name: &str) -> Result<Self> {
if size == 0 {
return Err(StatusCode::BadValue);
}
let size = round_up_to_page(size).ok_or(StatusCode::BadValue)?;
let Created { fd, ro_fd } = backend::create(name, size)?;
let map = if flags & FLAG_DONT_MAP_LOCALLY == 0 {
Some(Mapping::map(&fd, size, 0, true)?)
} else {
None
};
backend::seal(&fd, flags)?;
Ok(Self(HeapInner {
fd,
ro_fd,
export_ro: AtomicBool::new(flags & FLAG_READ_ONLY != 0),
map,
size,
offset: 0,
flags,
writable: true,
}))
}
pub fn as_ptr(&self) -> Option<*mut u8> {
self.0.as_ptr()
}
pub fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<()> {
self.0.read_at(off, dst)
}
pub fn write_at(&self, off: usize, src: &[u8]) -> Result<()> {
self.0.write_at(off, src)
}
pub fn seals(&self) -> Option<u32> {
self.0.seals()
}
pub fn seal_future_write(&self) -> Result<()> {
self.0.seal_future_write()
}
pub fn to_parcel_fd(&self) -> Result<ParcelFileDescriptor> {
self.0.to_parcel_fd()
}
}
impl AsFd for MemoryHeapBase {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
self.0.export_fd().as_fd()
}
}
impl IMemoryHeap for MemoryHeapBase {
fn heap_id(&self) -> i32 {
self.0.impl_heap_id()
}
fn size(&self) -> usize {
self.0.size
}
fn flags(&self) -> u32 {
self.0.flags
}
fn offset(&self) -> usize {
self.0.offset
}
fn base(&self) -> Option<SharedBytes<'_>> {
self.0.base()
}
}
pub struct MappedHeap(HeapInner);
impl std::fmt::Debug for MappedHeap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MappedHeap")
.field("fd", &self.0.impl_heap_id())
.field("size", &self.0.size)
.field("offset", &self.0.offset)
.field("flags", &format_args!("{:#x}", self.0.flags))
.finish()
}
}
impl MappedHeap {
pub fn from_fd(fd: OwnedFd, size: usize, offset: usize, flags: u32) -> Result<Self> {
if size == 0 || offset % page_size() != 0 {
return Err(StatusCode::BadValue);
}
let end = offset.checked_add(size).ok_or(StatusCode::BadValue)?;
let st_size = rustix::fs::fstat(&fd)?.st_size;
if st_size <= 0 {
if !super::shared::is_ashmem_fd(fd.as_fd()) {
log::error!("shared memory fd has no backing size and is not ashmem");
return Err(StatusCode::BadValue);
}
} else if (end as u64) > st_size as u64 {
return Err(StatusCode::BadValue);
}
let writable = flags & FLAG_READ_ONLY == 0;
let map = Mapping::map(&fd, size, offset, writable)?;
Ok(Self(HeapInner {
fd,
ro_fd: None,
export_ro: AtomicBool::new(false),
map: Some(map),
size,
offset,
flags,
writable,
}))
}
pub fn from_fd_strict(fd: OwnedFd, size: usize, offset: usize, flags: u32) -> Result<Self> {
let seals = backend::get_seals(&fd).ok_or(StatusCode::InvalidOperation)?;
if seals & SEAL_SHRINK == 0 {
return Err(StatusCode::BadValue);
}
if flags & FLAG_READ_ONLY != 0 && seals & (SEAL_WRITE | SEAL_FUTURE_WRITE) == 0 {
return Err(StatusCode::BadValue);
}
Self::from_fd(fd, size, offset, flags)
}
pub fn from_parcel_fd(
pfd: ParcelFileDescriptor,
size: usize,
offset: usize,
flags: u32,
) -> Result<Self> {
Self::from_fd(OwnedFd::from(pfd), size, offset, flags)
}
pub fn as_ptr(&self) -> Option<*mut u8> {
self.0.as_ptr()
}
pub fn read_at(&self, off: usize, dst: &mut [u8]) -> Result<()> {
self.0.read_at(off, dst)
}
pub fn write_at(&self, off: usize, src: &[u8]) -> Result<()> {
self.0.write_at(off, src)
}
pub fn seals(&self) -> Option<u32> {
self.0.seals()
}
pub fn seal_future_write(&self) -> Result<()> {
self.0.seal_future_write()
}
pub fn to_parcel_fd(&self) -> Result<ParcelFileDescriptor> {
self.0.to_parcel_fd()
}
}
impl AsFd for MappedHeap {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
self.0.export_fd().as_fd()
}
}
impl IMemoryHeap for MappedHeap {
fn heap_id(&self) -> i32 {
self.0.impl_heap_id()
}
fn size(&self) -> usize {
self.0.size
}
fn flags(&self) -> u32 {
self.0.flags
}
fn offset(&self) -> usize {
self.0.offset
}
fn base(&self) -> Option<SharedBytes<'_>> {
self.0.base()
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "android", target_os = "macos")
))]
mod tests {
use super::*;
use std::os::fd::AsRawFd;
#[test]
fn new_rounds_size_up_to_page_and_maps() {
let h = MemoryHeapBase::new(100, 0).unwrap();
assert_eq!(h.size(), page_size());
assert_eq!(h.offset(), 0);
assert!(h.as_ptr().is_some());
assert_eq!(h.base().unwrap().len(), page_size());
assert!(h.heap_id() >= 0);
}
#[test]
fn zero_size_is_bad_value() {
assert_eq!(MemoryHeapBase::new(0, 0).unwrap_err(), StatusCode::BadValue);
}
#[test]
fn dont_map_locally_skips_mapping() {
let h = MemoryHeapBase::new(4096, FLAG_DONT_MAP_LOCALLY).unwrap();
assert!(h.as_ptr().is_none());
assert!(h.base().is_none());
assert_eq!(
h.read_at(0, &mut [0u8; 4]).unwrap_err(),
StatusCode::InvalidOperation
);
assert_eq!(h.flags(), FLAG_DONT_MAP_LOCALLY);
}
#[test]
fn write_then_read_roundtrip_and_bounds() {
let h = MemoryHeapBase::new(1, 0).unwrap();
let ps = page_size();
h.write_at(10, b"hello").unwrap();
let mut buf = [0u8; 5];
h.read_at(10, &mut buf).unwrap();
assert_eq!(&buf, b"hello");
h.write_at(ps - 5, b"12345").unwrap();
assert_eq!(
h.write_at(ps - 4, b"12345").unwrap_err(),
StatusCode::BadValue
);
assert_eq!(
h.read_at(usize::MAX, &mut buf).unwrap_err(),
StatusCode::BadValue
);
let base = h.base().unwrap();
assert_eq!(base.slice(10, 5).unwrap().to_vec(), b"hello");
assert_eq!(base.load(10), Some(b'h'));
assert_eq!(base.load(ps), None);
assert!(base.slice(ps - 1, 2).is_none());
let mut two = [0u8; 2];
assert_eq!(
base.copy_to(ps - 1, &mut two).unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn unaligned_copies_round_trip_and_preserve_neighbours() {
let ps = page_size();
let owner = MemoryHeapBase::new(ps, 0).unwrap();
let odd = ps - 3;
let rx = MappedHeap::from_parcel_fd(owner.to_parcel_fd().unwrap(), odd, 0, 0).unwrap();
let pattern: Vec<u8> = (0..odd).map(|i| (i * 7 % 251) as u8).collect();
rx.write_at(0, &pattern).unwrap();
for (off, len) in [
(0, 1),
(3, 5),
(5, 11),
(7, 1),
(odd - 20, 20),
(odd - 1, 1),
(0, odd),
] {
let mut got = vec![0u8; len];
rx.read_at(off, &mut got).unwrap();
assert_eq!(got, &pattern[off..off + len], "off={off} len={len}");
owner.read_at(off, &mut got).unwrap();
assert_eq!(got, &pattern[off..off + len], "owner off={off} len={len}");
}
rx.write_at(9, b"xyz").unwrap();
let mut word = [0u8; 16];
rx.read_at(8, &mut word).unwrap();
assert_eq!(&word[..1], &pattern[8..9]);
assert_eq!(&word[1..4], b"xyz");
assert_eq!(&word[4..], &pattern[12..24]);
assert_eq!(rx.base().unwrap().len(), odd);
assert_eq!(rx.base().unwrap().to_vec()[..8], pattern[..8]);
}
#[test]
fn owner_of_read_only_heap_can_still_write() {
let h = MemoryHeapBase::new(4096, FLAG_READ_ONLY).unwrap();
h.write_at(0, b"owner").unwrap();
}
#[test]
fn drop_releases_fd_and_mapping() {
for _ in 0..2048 {
let h = MemoryHeapBase::new(1, 0).unwrap();
let _rx =
MappedHeap::from_parcel_fd(h.to_parcel_fd().unwrap(), h.size(), 0, 0).unwrap();
}
}
#[test]
fn dup_fd_maps_the_same_pages_in_process() {
let ps = page_size();
let owner = MemoryHeapBase::new(ps * 2, 0).unwrap();
let pfd = owner.to_parcel_fd().unwrap();
let rx = MappedHeap::from_parcel_fd(pfd, owner.size(), 0, owner.flags()).unwrap();
owner.write_at(ps, b"shared-page").unwrap();
let mut buf = [0u8; 11];
rx.read_at(ps, &mut buf).unwrap();
assert_eq!(&buf, b"shared-page");
rx.write_at(0, b"back").unwrap();
let mut back = [0u8; 4];
owner.read_at(0, &mut back).unwrap();
assert_eq!(&back, b"back");
}
#[test]
fn receiver_honours_read_only_flag() {
let owner = MemoryHeapBase::new(4096, FLAG_READ_ONLY).unwrap();
owner.write_at(0, b"ro").unwrap();
let rx = MappedHeap::from_parcel_fd(
owner.to_parcel_fd().unwrap(),
owner.size(),
0,
FLAG_READ_ONLY,
)
.unwrap();
let mut b = [0u8; 2];
rx.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"ro");
assert_eq!(
rx.write_at(0, b"xx").unwrap_err(),
StatusCode::PermissionDenied
);
}
#[test]
fn from_fd_rejects_bad_geometry() {
let owner = MemoryHeapBase::new(1, 0).unwrap();
let ps = page_size();
let fd = || OwnedFd::from(owner.to_parcel_fd().unwrap());
assert_eq!(
MappedHeap::from_fd(fd(), 0, 0, 0).unwrap_err(),
StatusCode::BadValue
);
assert_eq!(
MappedHeap::from_fd(fd(), ps, 1, 0).unwrap_err(),
StatusCode::BadValue
);
assert_eq!(
MappedHeap::from_fd(fd(), ps * 2, 0, 0).unwrap_err(),
StatusCode::BadValue
);
assert_eq!(
MappedHeap::from_fd(fd(), ps, ps, 0).unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn to_parcel_fd_is_cloexec_and_independent() {
let owner = MemoryHeapBase::new(4096, 0).unwrap();
let pfd = owner.to_parcel_fd().unwrap();
assert_ne!(pfd.as_raw_fd(), owner.heap_id());
let flags = rustix::io::fcntl_getfd(pfd.as_ref()).unwrap();
assert!(flags.contains(rustix::io::FdFlags::CLOEXEC));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux {
use super::*;
#[test]
fn seal_future_write_needs_allow_sealing() {
assert_eq!(
MemoryHeapBase::new(4096, 0)
.unwrap()
.seal_future_write()
.unwrap_err(),
StatusCode::InvalidOperation
);
let h = MemoryHeapBase::new(4096, FLAG_MEMFD_ALLOW_SEALING).unwrap();
h.seal_future_write().unwrap();
assert_ne!(h.seals().unwrap() & SEAL_FUTURE_WRITE, 0);
}
#[test]
fn mapped_heap_reports_seal_seal_as_invalid_operation() {
let h = MemoryHeapBase::new(4096, 0).unwrap();
let rx = MappedHeap::from_parcel_fd(h.to_parcel_fd().unwrap(), h.size(), 0, 0).unwrap();
assert_eq!(
rx.seal_future_write().unwrap_err(),
StatusCode::InvalidOperation
);
let h = MemoryHeapBase::new(4096, FLAG_MEMFD_ALLOW_SEALING).unwrap();
let rx = MappedHeap::from_parcel_fd(h.to_parcel_fd().unwrap(), h.size(), 0, 0).unwrap();
rx.seal_future_write().unwrap();
assert_ne!(rx.seals().unwrap() & SEAL_FUTURE_WRITE, 0);
}
#[test]
fn default_seals_are_grow_shrink_seal() {
let h = MemoryHeapBase::new(4096, 0).unwrap();
let s = h.seals().unwrap();
assert_eq!(
s & (SEAL_GROW | SEAL_SHRINK | SEAL_SEAL),
SEAL_GROW | SEAL_SHRINK | SEAL_SEAL
);
assert_eq!(s & SEAL_FUTURE_WRITE, 0);
}
#[test]
fn read_only_adds_future_write_and_allow_sealing_omits_seal() {
let h = MemoryHeapBase::new(4096, FLAG_READ_ONLY | FLAG_MEMFD_ALLOW_SEALING).unwrap();
let s = h.seals().unwrap();
assert_ne!(s & SEAL_FUTURE_WRITE, 0);
assert_eq!(s & SEAL_SEAL, 0);
}
#[test]
fn sealed_fd_cannot_grow() {
let h = MemoryHeapBase::new(1, 0).unwrap();
assert!(rustix::fs::ftruncate(&h, (h.size() * 2) as u64).is_err());
}
#[test]
fn strict_accepts_sealed_and_rejects_unsealed() {
let h = MemoryHeapBase::new(4096, FLAG_READ_ONLY).unwrap();
let fd = OwnedFd::from(h.to_parcel_fd().unwrap());
MappedHeap::from_fd_strict(fd, h.size(), 0, FLAG_READ_ONLY).unwrap();
let ps = page_size();
let raw =
rustix::fs::memfd_create("unsealed", rustix::fs::MemfdFlags::CLOEXEC).unwrap();
rustix::fs::ftruncate(&raw, ps as u64).unwrap();
assert_eq!(
MappedHeap::from_fd_strict(raw, ps, 0, 0).unwrap_err(),
StatusCode::BadValue
);
let rw = MemoryHeapBase::new(4096, 0).unwrap();
let fd = || OwnedFd::from(rw.to_parcel_fd().unwrap());
MappedHeap::from_fd_strict(fd(), ps, 0, 0).unwrap();
assert_eq!(
MappedHeap::from_fd_strict(fd(), ps, 0, FLAG_READ_ONLY).unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn future_write_seal_blocks_new_writable_mapping() {
let h = MemoryHeapBase::new(4096, FLAG_READ_ONLY).unwrap();
let fd = OwnedFd::from(h.to_parcel_fd().unwrap());
assert!(MappedHeap::from_fd(fd, h.size(), 0, 0).is_err());
}
}
#[cfg(target_os = "macos")]
mod macos {
use super::*;
#[test]
fn seals_are_synthesized_from_fd_mode() {
let rw = MemoryHeapBase::new(1, 0).unwrap();
assert_eq!(rw.seals(), Some(SEAL_SHRINK | SEAL_GROW));
let ro = MemoryHeapBase::new(1, FLAG_READ_ONLY).unwrap();
assert_eq!(
ro.seals(),
Some(SEAL_SHRINK | SEAL_GROW | SEAL_FUTURE_WRITE)
);
let pfd = ro.to_parcel_fd().unwrap();
let fl = rustix::fs::fcntl_getfl(pfd.as_ref()).unwrap();
assert_eq!(fl & rustix::fs::OFlags::ACCMODE, rustix::fs::OFlags::RDONLY);
}
#[test]
fn regular_file_is_not_shrink_protected() {
let path = std::env::temp_dir().join(format!("rsb-shm-probe-{}", std::process::id()));
let f = std::fs::File::options()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&path)
.unwrap();
f.set_len(page_size() as u64).unwrap();
let fd = OwnedFd::from(f);
let probe = MappedHeap::from_fd(fd.try_clone().unwrap(), page_size(), 0, 0).unwrap();
assert_eq!(probe.seals(), None);
assert_eq!(
MappedHeap::from_fd_strict(fd, page_size(), 0, 0).unwrap_err(),
StatusCode::InvalidOperation
);
drop(probe);
let _ = std::fs::remove_file(path);
}
#[test]
fn second_ftruncate_is_refused_by_the_kernel() {
let h = MemoryHeapBase::new(1, 0).unwrap();
let fd = OwnedFd::from(h.to_parcel_fd().unwrap());
assert!(rustix::fs::ftruncate(&fd, (h.size() / 2) as u64).is_err());
assert!(rustix::fs::ftruncate(&fd, (h.size() * 2) as u64).is_err());
}
#[test]
fn lying_peer_cannot_map_read_only_export_writable() {
let h = MemoryHeapBase::new(1, FLAG_READ_ONLY).unwrap();
let fd = OwnedFd::from(h.to_parcel_fd().unwrap());
assert!(MappedHeap::from_fd(fd, h.size(), 0, 0).is_err());
h.write_at(0, b"ro-ok").unwrap();
let rx =
MappedHeap::from_parcel_fd(h.to_parcel_fd().unwrap(), h.size(), 0, FLAG_READ_ONLY)
.unwrap();
let mut b = [0u8; 5];
rx.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"ro-ok");
assert_eq!(
rx.seals(),
Some(SEAL_SHRINK | SEAL_GROW | SEAL_FUTURE_WRITE)
);
}
#[test]
fn strict_accepts_ro_export_and_rejects_rw_for_read_only_claim() {
let ro = MemoryHeapBase::new(1, FLAG_READ_ONLY).unwrap();
MappedHeap::from_fd_strict(
OwnedFd::from(ro.to_parcel_fd().unwrap()),
ro.size(),
0,
FLAG_READ_ONLY,
)
.unwrap();
let rw = MemoryHeapBase::new(1, 0).unwrap();
MappedHeap::from_fd_strict(OwnedFd::from(rw.to_parcel_fd().unwrap()), rw.size(), 0, 0)
.unwrap();
assert_eq!(
MappedHeap::from_fd_strict(
OwnedFd::from(rw.to_parcel_fd().unwrap()),
rw.size(),
0,
FLAG_READ_ONLY
)
.unwrap_err(),
StatusCode::BadValue
);
}
#[test]
fn seal_future_write_switches_export_to_read_only() {
let h = MemoryHeapBase::new(1, 0).unwrap();
let before = OwnedFd::from(h.to_parcel_fd().unwrap());
h.seal_future_write().unwrap();
h.write_at(0, b"owner").unwrap(); let after = OwnedFd::from(h.to_parcel_fd().unwrap());
assert!(MappedHeap::from_fd(after, h.size(), 0, 0).is_err());
assert!(MappedHeap::from_fd(before, h.size(), 0, 0).is_ok());
let rw = MemoryHeapBase::new(1, 0).unwrap();
let rx_rw =
MappedHeap::from_parcel_fd(rw.to_parcel_fd().unwrap(), rw.size(), 0, 0).unwrap();
assert_eq!(
rx_rw.seal_future_write().unwrap_err(),
StatusCode::InvalidOperation
);
let rx_ro =
MappedHeap::from_parcel_fd(h.to_parcel_fd().unwrap(), h.size(), 0, FLAG_READ_ONLY)
.unwrap();
rx_ro.seal_future_write().unwrap();
assert!(backend::is_read_only_fd(h.as_fd()));
assert!(!backend::is_read_only_fd(rw.as_fd()));
}
}
}