//! Native rights-attenuating handle table. See os-handles/rights/CONTRACT.md. //! A caller must bind `P` to a live kernel process ID before invoking this //! table. `Object` IDs are kernel-owned names, never user pointers. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Handle(u64); impl Handle { pub const fn from_raw(raw: u64) -> Self { Self(raw) } pub const fn raw(self) -> u64 { self.0 } pub const fn slot(self) -> u32 { self.0 as u32 } pub const fn generation(self) -> u32 { (self.0 >> 32) as u32 } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Object { File(u64), Channel(u64), Task(u64), } impl Object { const fn id(self) -> u64 { match self { Self::File(id) | Self::Channel(id) | Self::Task(id) => id, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rights(u8); impl Rights { pub const READ: Self = Self(1); pub const WRITE: Self = Self(2); pub const READ_WRITE: Self = Self(3); pub const fn contains(self, required: Self) -> bool { self.0 & required.0 == required.0 } pub const fn is_nonempty_subset_of(self, original: Self) -> bool { self.0 != 0 && original.contains(self) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { BadFd, Access, InvalidObject, Full, IdExhausted, } impl Error { pub const fn errno(self) -> u64 { match self { Self::BadFd => 9, Self::Access => 13, Self::InvalidObject => 22, Self::Full => 24, Self::IdExhausted => 75, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Entry

{ handle: Handle, owner: P, object: Object, rights: Rights, } /// Complete scalar audit state. It does not expose a mutable table entry. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub generations: [u32; N], pub entries: [Option<(u64, P, Object, Rights)>; N], } pub struct Table { entries: [Option>; N], generations: [u32; N], } impl Table { pub const fn new() -> Self { Self { entries: [None; N], generations: [0; N] } } /// Kernel-only deterministic seed for an isolated overflow probe. A user /// syscall adapter must never take the seed from caller input. pub const fn with_generation_seed(generations: [u32; N]) -> Self { Self { entries: [None; N], generations } } pub fn len(&self) -> usize { self.entries.iter().filter(|entry| entry.is_some()).count() } pub fn owned(&self, owner: P) -> usize { self.entries.iter().flatten().filter(|entry| entry.owner == owner).count() } /// Validate all fallible conditions before publishing the entry. pub fn open(&mut self, owner: P, object: Object) -> Result { let rights = match object { Object::File(_) => Rights::READ_WRITE, Object::Channel(_) => Rights::WRITE, Object::Task(_) => Rights::READ, }; self.open_with_rights(owner, object, rights) } pub fn open_with_rights(&mut self, owner: P, object: Object, rights: Rights) -> Result { if object.id() == 0 { return Err(Error::InvalidObject); } let allowed = match object { Object::File(_) => Rights::READ_WRITE, Object::Channel(_) => Rights::WRITE, Object::Task(_) => Rights::READ, }; if !rights.is_nonempty_subset_of(allowed) { return Err(Error::Access); } let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?; let slot_id = u32::try_from(slot).ok() .and_then(|value| value.checked_add(1)).ok_or(Error::IdExhausted)?; let generation = self.generations[slot] .checked_add(1).ok_or(Error::IdExhausted)?; let handle = Handle((u64::from(generation) << 32) | u64::from(slot_id)); self.entries[slot] = Some(Entry { handle, owner, object, rights }); self.generations[slot] = generation; Ok(handle) } fn entry(&self, owner: P, handle: Handle) -> Result, Error> { let slot = handle.slot().checked_sub(1) .and_then(|value| usize::try_from(value).ok()).ok_or(Error::BadFd)?; self.entries.get(slot).copied().flatten() .filter(|entry| entry.owner == owner && entry.handle == handle) .ok_or(Error::BadFd) } /// A File handle cannot be used through the Channel or Task API. pub fn file_read(&self, owner: P, handle: Handle) -> Result { let entry = self.entry(owner, handle)?; match entry.object { Object::File(id) if entry.rights.contains(Rights::READ) => Ok(id), Object::File(_) => Err(Error::Access), _ => Err(Error::BadFd), } } pub fn file_write(&self, owner: P, handle: Handle) -> Result { let entry = self.entry(owner, handle)?; match entry.object { Object::File(id) if entry.rights.contains(Rights::WRITE) => Ok(id), Object::File(_) => Err(Error::Access), _ => Err(Error::BadFd), } } pub fn channel_send(&self, owner: P, handle: Handle) -> Result { let entry = self.entry(owner, handle)?; match entry.object { Object::Channel(id) if entry.rights.contains(Rights::WRITE) => Ok(id), Object::Channel(_) => Err(Error::Access), _ => Err(Error::BadFd), } } pub fn task_inspect(&self, owner: P, handle: Handle) -> Result { let entry = self.entry(owner, handle)?; match entry.object { Object::Task(id) if entry.rights.contains(Rights::READ) => Ok(id), Object::Task(_) => Err(Error::Access), _ => Err(Error::BadFd), } } /// Atomically publish a second reference only with a rights subset. pub fn duplicate(&mut self, owner: P, source: Handle, requested: Rights) -> Result { let original = self.entry(owner, source)?; if !requested.is_nonempty_subset_of(original.rights) { return Err(Error::Access); } let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?; let slot_id = u32::try_from(slot).ok() .and_then(|value| value.checked_add(1)).ok_or(Error::IdExhausted)?; let generation = self.generations[slot] .checked_add(1).ok_or(Error::IdExhausted)?; let handle = Handle((u64::from(generation) << 32) | u64::from(slot_id)); self.entries[slot] = Some(Entry {handle, owner, object: original.object, rights: requested}); self.generations[slot] = generation; Ok(handle) } pub fn close(&mut self, owner: P, handle: Handle) -> Result<(), Error> { let slot = self.entries.iter().position(|entry| entry.is_some_and( |entry| entry.owner == owner && entry.handle == handle)) .ok_or(Error::BadFd)?; self.entries[slot] = None; Ok(()) } pub fn close_owner(&mut self, owner: P) -> usize { let mut closed = 0; for entry in &mut self.entries { if entry.is_some_and(|entry| entry.owner == owner) { *entry = None; closed += 1; } } closed } pub fn snapshot(&self) -> Snapshot { Snapshot { generations: self.generations, entries: self.entries.map(|entry| entry.map( |entry| (entry.handle.raw(), entry.owner, entry.object, entry.rights))) } } } impl Default for Table { fn default() -> Self { Self::new() } }