blob: 08287cbca16ea90519ab278b58b2b3561cefb9a5 [file] [edit]
// 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())
}
}
}