| // Copyright 2025 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| use core::cmp::min; |
| use core::ptr::NonNull; |
| |
| use memory_config::MemoryRegionType; |
| use pw_status::{Error, Result}; |
| use vectored_buffer::{Slice, vectored_buffer_copy}; |
| |
| use crate::Kernel; |
| use crate::scheduler::SchedulerState; |
| use crate::scheduler::thread::ProcessHandle; |
| use crate::sync::spinlock::SpinLockGuard; |
| |
| /// A `Buffer` represents a buffer or vector of buffers in a user process. |
| pub enum Buffer { |
| Flat(Slice<u8>), |
| Vector(Slice<Slice<u8>>), |
| } |
| |
| impl Buffer { |
| fn new(address: usize, length: isize) -> Result<Self> { |
| let Some(address) = NonNull::<u8>::new(core::ptr::with_exposed_provenance_mut(address)) |
| else { |
| return Err(Error::InvalidArgument); |
| }; |
| if length >= 0 { |
| Ok(Buffer::Flat(Slice { |
| addr: address, |
| length: length.cast_unsigned(), |
| })) |
| } else { |
| Ok(Buffer::Vector(Slice { |
| addr: address.cast(), |
| length: (-length).cast_unsigned(), |
| })) |
| } |
| } |
| |
| fn _check_access_locked<K: Kernel>( |
| sched: &SpinLockGuard<'_, K, SchedulerState<K>>, |
| start: usize, |
| size: usize, |
| access_type: MemoryRegionType, |
| process_handle: &ProcessHandle<K>, |
| ) -> Result<usize> { |
| if size > 0 { |
| // We only need to check if the size is greater than zero. |
| // The pointer for zero-sized objects will be invalid, so |
| // we shouldn't check access. |
| if !process_handle.range_has_access_locked(sched, access_type, start..(start + size)) { |
| return Err(Error::PermissionDenied); |
| } |
| } |
| Ok(size) |
| } |
| fn check_access_locked<K: Kernel>( |
| &self, |
| sched: &SpinLockGuard<'_, K, SchedulerState<K>>, |
| access_type: MemoryRegionType, |
| process_handle: &ProcessHandle<K>, |
| ) -> Result<usize> { |
| let total_size = match self { |
| Buffer::Flat(buf) => Self::_check_access_locked( |
| sched, |
| buf.addr.as_ptr().addr(), |
| buf.length, |
| access_type, |
| process_handle, |
| )?, |
| |
| Buffer::Vector(vec) => { |
| let mut sum = 0; |
| // First make sure we can access the vector of slices. |
| let _ = Self::_check_access_locked( |
| sched, |
| vec.addr.as_ptr().addr(), |
| vec.length * core::mem::size_of::<Slice<u8>>(), |
| access_type, |
| process_handle, |
| )?; |
| // Then check each item in the vector and sum up the total size in bytes. |
| for item in vec.as_slice() { |
| sum += Self::_check_access_locked( |
| sched, |
| item.addr.as_ptr().addr(), |
| item.length, |
| access_type, |
| process_handle, |
| )?; |
| } |
| sum |
| } |
| }; |
| Ok(total_size) |
| } |
| |
| #[must_use] |
| fn as_slices(&self) -> &[Slice<u8>] { |
| match self { |
| Buffer::Flat(buf) => core::slice::from_ref(buf), |
| Buffer::Vector(vec) => vec.as_slice(), |
| } |
| } |
| } |
| |
| // A buffer that resides in a process |
| // |
| // INVARIANTS: |
| // * All access to the underlying memory is done through the `UserBuffer`. The |
| // `UserBuffer` will never expose a pointer or reference to the underlying memory. |
| // Additionally `UserBuffer` is in its own leaf module to guard against other |
| // code having access to its private fields. |
| // * Operation on the buffer always respects the access rights of the process to which |
| // it is bound. |
| pub struct SyscallBuffer { |
| // Pointer to the user buffer. |
| buffer: Buffer, |
| // The sum of the sizes of all the buffers in the iovec. |
| size: usize, |
| access_type: MemoryRegionType, |
| } |
| |
| impl SyscallBuffer { |
| /// Create a new buffer with access rights bound to the current process. |
| pub fn new_in_current_process<K: Kernel>( |
| kernel: K, |
| access_type: MemoryRegionType, |
| address: usize, |
| length: isize, |
| ) -> Result<Self> { |
| let sched = kernel.get_scheduler().lock(kernel); |
| let process_handle = sched.current_thread().process(); |
| let buffer = Buffer::new(address, length)?; |
| |
| // SAFETY: Since there is no dynamic memory and processes can not be |
| // terminated and restarted, the access check can happen at buffer creates |
| // and still uphold the invariant that the processes access rights are respected. |
| // |
| // TODO: https://pwbug.dev/442660183 - Handle access checks with process termination. |
| let size = buffer.check_access_locked(&sched, access_type, &process_handle)?; |
| Ok(Self { |
| buffer, |
| size, |
| access_type, |
| }) |
| } |
| |
| /// Returns the size of the buffer. |
| #[must_use] |
| pub fn size(&self) -> usize { |
| self.size |
| } |
| |
| /// Reduce the size of the buffer |
| /// |
| /// # Panics |
| /// Panics if new_len is greater than the current buffer length. |
| pub fn truncate(&mut self, new_size: usize) { |
| pw_assert::assert!(new_size <= self.size); |
| self.size = new_size; |
| } |
| |
| /// Copy from this buffer into another. |
| /// |
| /// Copies data from this buffer into `into_buffer`, starting at `offset` bytes |
| /// from the beginning of this buffer. Will copy maximum number of bytes |
| /// that exist and this buffer and will fit into `into_buffer` |
| /// |
| /// # Returns |
| /// - Ok(0): No data was copied. This can occur if `offset` is equal to the |
| /// size of the buffer or `into_buffer is of size zero. |
| /// - Ok(len): Returns the number of bytes copied. |
| /// - Error::PermissionDenied: Either this buffer is not readable or `into_buffer` |
| /// is not writeable |
| /// - Error::OutOfRange: `offset` is larger than the size of the buffer. |
| pub fn copy_into(&self, offset: usize, into_buffer: &mut SyscallBuffer) -> Result<usize> { |
| if !self.access_type.is_readable() || !into_buffer.access_type.is_writeable() { |
| return Err(Error::PermissionDenied); |
| } |
| |
| if offset > self.size { |
| return Err(Error::OutOfRange); |
| } |
| |
| let available_bytes = self.size - offset; |
| let copy_len = min(available_bytes, into_buffer.size); |
| |
| // SAFETY: The access right invariant is upheld at buffer creation time |
| // where the addr and size fields are validated. |
| let copied = vectored_buffer_copy( |
| offset, |
| copy_len, |
| self.buffer.as_slices(), |
| into_buffer.buffer.as_slices(), |
| ); |
| Ok(copied) |
| } |
| |
| #[must_use] |
| pub fn as_slices(&self) -> &[&[u8]] { |
| unsafe { |
| // Safety: Address and size are checked and validated in `new()`. |
| // The `Slice` type is isomorphic to the underlying rust representation of a slice. |
| core::mem::transmute(self.buffer.as_slices()) |
| } |
| } |
| } |