kernel/platform/
mpu.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Interface for configuring the Memory Protection Unit.
6
7use core::fmt::{self, Display};
8
9/// User mode access permissions.
10#[derive(Copy, Clone, Debug)]
11pub enum Permissions {
12    ReadWriteExecute,
13    ReadWriteOnly,
14    ReadExecuteOnly,
15    ReadOnly,
16    ExecuteOnly,
17}
18
19/// MPU region.
20///
21/// This is one contiguous address space protected by the MPU.
22#[derive(Copy, Clone, PartialEq, Eq)]
23pub struct Region {
24    /// The memory address where the region starts.
25    ///
26    /// For maximum compatibility, we use a u8 pointer, however, note that many
27    /// memory protection units have very strict alignment requirements for the
28    /// memory regions protected by the MPU.
29    start_address: *const u8,
30
31    /// The number of bytes of memory in the MPU region.
32    size: usize,
33}
34
35impl Region {
36    /// Create a new MPU region with a given starting point and length in bytes.
37    pub fn new(start_address: *const u8, size: usize) -> Region {
38        Region {
39            start_address,
40            size,
41        }
42    }
43
44    /// Getter: retrieve the address of the start of the MPU region.
45    pub fn start_address(&self) -> *const u8 {
46        self.start_address
47    }
48
49    /// Getter: retrieve the length of the region in bytes.
50    pub fn size(&self) -> usize {
51        self.size
52    }
53}
54
55/// Null type for the default type of the `MpuConfig` type in an implementation
56/// of the `MPU` trait.
57///
58/// This custom type allows us to implement `Display` with an empty
59/// implementation to meet the constraint on `type MpuConfig`.
60pub struct MpuConfigDefault;
61
62impl Display for MpuConfigDefault {
63    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        Ok(())
65    }
66}
67
68/// The generic trait that particular memory protection unit implementations
69/// need to implement.
70///
71/// This trait is a blend of relatively generic MPU functionality that should be
72/// common across different MPU implementations, and more specific requirements
73/// that Tock needs to support protecting applications. While a less
74/// Tock-specific interface may be desirable, due to the sometimes complex
75/// alignment rules and other restrictions imposed by MPU hardware, some of the
76/// Tock details have to be passed into this interface. That allows the MPU
77/// implementation to have more flexibility when satisfying the protection
78/// requirements, and also allows the MPU to specify some addresses used by the
79/// kernel when deciding where to place certain application memory regions so
80/// that the MPU can appropriately provide protection for those memory regions.
81///
82/// # Safety
83///
84/// This is an `unsafe trait`, as it is crucial to uphold Tock's isolation
85/// properties, and thus safety of the Tock kernel. Users of this trait must be
86/// able to rely on its implementations being correct. Specifically, they must
87/// ensure that applications only have access to the configured MPU regions and
88/// no kernel memory.
89pub unsafe trait MPU {
90    /// MPU-specific state that defines a particular configuration for the MPU.
91    /// That is, this should contain all of the required state such that the
92    /// implementation can be passed an object of this type and it should be
93    /// able to correctly and entirely configure the MPU.
94    ///
95    /// This state will be held on a per-process basis as a way to cache all of
96    /// the process settings. When the kernel switches to a new process it will
97    /// use the `MpuConfig` for that process to quickly configure the MPU.
98    ///
99    /// It is `Default` so we can create empty state when the process is
100    /// created, and `Display` so that the `panic!()` output can display the
101    /// current state to help with debugging.
102    type MpuConfig: Display;
103
104    /// Enables the MPU for userspace apps.
105    ///
106    /// This function must enable the permission restrictions on the various
107    /// regions protected by the MPU.
108    fn enable_app_mpu(&self);
109
110    /// Disables the MPU for userspace apps.
111    ///
112    /// This function must disable any memory protection rules that were
113    /// previously setup for an app, if those rules can prevent the kernel from
114    /// accessing this application's memory.
115    ///
116    /// This function is intended to be called when switching back from an
117    /// application to the kernel, as some platforms prevent kernel-mode from
118    /// accessing memory made accessible to less-privileged applications. This
119    /// restriction is intended to increase security by making it harder to
120    /// inject application-controlled data into a buggy kernel (e.g., through an
121    /// incorrect context switch application that provides kernel-mode
122    /// privileges to applications). However, it also prevents the kernel from
123    /// managing application state or accessing application-provided data.
124    ///
125    /// # Safety
126    ///
127    /// This function is marked `unsafe`, as invoking it and subsequently
128    /// switching back to an application could, depending on the underlying MPU
129    /// implementation, provide that application access to kernel memory. Before
130    /// switching back to an application, and after calling this function, the
131    /// kernel must first re-enable memory protection through a call to
132    /// [`MPU::enable_app_mpu`].
133    unsafe fn disable_app_mpu(&self);
134
135    /// Returns the maximum number of regions supported by the MPU.
136    fn number_total_regions(&self) -> usize;
137
138    /// Creates a new empty MPU configuration.
139    ///
140    /// The returned configuration must not have any userspace-accessible
141    /// regions pre-allocated.
142    ///
143    /// The underlying implementation may only be able to allocate a finite
144    /// number of MPU configurations. It may return `None` if this resource is
145    /// exhausted.
146    fn new_config(&self) -> Option<Self::MpuConfig>;
147
148    /// Resets an MPU configuration.
149    ///
150    /// This method resets an MPU configuration to its initial state, as
151    /// returned by [`MPU::new_config`]. After invoking this operation, it must
152    /// not have any userspace-acessible regions pre-allocated.
153    fn reset_config(&self, config: &mut Self::MpuConfig);
154
155    /// Allocates a new MPU region.
156    ///
157    /// An implementation must allocate an MPU region at least `min_region_size`
158    /// bytes in size within the specified stretch of unallocated memory, and
159    /// with the specified user mode permissions, and store it in `config`. The
160    /// allocated region may not overlap any of the regions already stored in
161    /// `config`.
162    ///
163    /// # Arguments
164    ///
165    /// - `unallocated_memory_start`: start of unallocated memory
166    /// - `unallocated_memory_size`:  size of unallocated memory
167    /// - `min_region_size`:          minimum size of the region
168    /// - `permissions`:              permissions for the region
169    /// - `config`:                   MPU region configuration
170    ///
171    /// # Return Value
172    ///
173    /// Returns the start and size of the allocated MPU region. If it is
174    /// infeasible to allocate the MPU region, returns None.
175    fn allocate_region(
176        &self,
177        unallocated_memory_start: *const u8,
178        unallocated_memory_size: usize,
179        min_region_size: usize,
180        permissions: Permissions,
181        config: &mut Self::MpuConfig,
182    ) -> Option<Region>;
183
184    /// Removes an MPU region within app-owned memory.
185    ///
186    /// An implementation must remove the MPU region that matches the region parameter if it exists.
187    /// If there is not a region that matches exactly, then the implementation may return an Error.
188    /// Implementors should not remove the app_memory_region and should return an Error if that
189    /// region is supplied.
190    ///
191    /// # Arguments
192    ///
193    /// - `region`:    a region previously allocated with `allocate_region`
194    /// - `config`:    MPU region configuration
195    ///
196    /// # Return Value
197    ///
198    /// Returns an error if the specified region is not exactly mapped to the process as specified
199    fn remove_memory_region(&self, region: Region, config: &mut Self::MpuConfig) -> Result<(), ()>;
200
201    /// Chooses the location for a process's memory, and allocates an MPU region
202    /// covering the app-owned part.
203    ///
204    /// An implementation must choose a contiguous block of memory that is at
205    /// least `min_memory_size` bytes in size and lies completely within the
206    /// specified stretch of unallocated memory.
207    ///
208    /// It must also allocate an MPU region with the following properties:
209    ///
210    /// 1. The region covers at least the first `initial_app_memory_size` bytes
211    ///    at the beginning of the memory block.
212    /// 2. The region does not overlap the last `initial_kernel_memory_size`
213    ///    bytes.
214    /// 3. The region has the user mode permissions specified by `permissions`.
215    ///
216    /// The end address of app-owned memory will increase in the future, so the
217    /// implementation should choose the location of the process memory block
218    /// such that it is possible for the MPU region to grow along with it. The
219    /// implementation must store the allocated region in `config`. The
220    /// allocated region may not overlap any of the regions already stored in
221    /// `config`.
222    ///
223    /// # Arguments
224    ///
225    /// - `unallocated_memory_start`:   start of unallocated memory
226    /// - `unallocated_memory_size`:    size of unallocated memory
227    /// - `min_memory_size`:            minimum total memory to allocate for process
228    /// - `initial_app_memory_size`:    initial size of app-owned memory
229    /// - `initial_kernel_memory_size`: initial size of kernel-owned memory
230    /// - `permissions`:                permissions for the MPU region
231    /// - `config`:                     MPU region configuration
232    ///
233    /// # Return Value
234    ///
235    /// This function returns the start address and the size of the memory block
236    /// chosen for the process. If it is infeasible to find a memory block or
237    /// allocate the MPU region, or if the function has already been called,
238    /// returns None. If None is returned no changes are made.
239    fn allocate_app_memory_region(
240        &self,
241        unallocated_memory_start: *const u8,
242        unallocated_memory_size: usize,
243        min_memory_size: usize,
244        initial_app_memory_size: usize,
245        initial_kernel_memory_size: usize,
246        permissions: Permissions,
247        config: &mut Self::MpuConfig,
248    ) -> Option<(*const u8, usize)>;
249
250    /// Updates the MPU region for app-owned memory.
251    ///
252    /// An implementation must reallocate the MPU region for app-owned memory
253    /// stored in `config` to maintain the 3 conditions described in
254    /// `allocate_app_memory_region`.
255    ///
256    /// # Arguments
257    ///
258    /// - `app_memory_break`:    new address for the end of app-owned memory
259    /// - `kernel_memory_break`: new address for the start of kernel-owned memory
260    /// - `permissions`:         permissions for the MPU region
261    /// - `config`:              MPU region configuration
262    ///
263    /// # Return Value
264    ///
265    /// Returns an error if it is infeasible to update the MPU region, or if it
266    /// was never created. If an error is returned no changes are made to the
267    /// configuration.
268    fn update_app_memory_region(
269        &self,
270        app_memory_break: *const u8,
271        kernel_memory_break: *const u8,
272        permissions: Permissions,
273        config: &mut Self::MpuConfig,
274    ) -> Result<(), ()>;
275
276    /// Configures the MPU with the provided region configuration.
277    ///
278    /// An implementation must ensure that, after the MPU configuration has been
279    /// activated through [`MPU::enable_app_mpu`] and until it has been disabled
280    /// through [`MPU::disable_app_mpu`], all memory locations not covered by an
281    /// allocated region are inaccessible in user mode, and all memory locations
282    /// covered by an allocated region are accessible in user mode.
283    ///
284    /// While the MPU configuration is active, memory locations covered by an
285    /// allocated region may or may not be accessible in kernel mode. When the
286    /// MPU configuration is not active, allocated regions must be accessible in
287    /// kernel mode.
288    ///
289    /// # Arguments
290    ///
291    /// - `config`: MPU region configuration
292    ///
293    /// # Safety
294    ///
295    /// This function is `unsafe` as incorrect use of it can endagner Tock's
296    /// isolation properties, and thus safety of the Tock kernel. Specifically,
297    /// callers of this function must ensure that they are applying an MPU
298    /// configuration that does not permit an application to access any
299    /// kernel-private memory (such as the grant region) or peripherals that can
300    /// transitively write to kernel-private memory (such as MMIO registers of
301    /// DMA-capable peripherals).
302    unsafe fn configure_mpu(&self, config: &Self::MpuConfig);
303}