Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@lschuermann
Copy link
Member

Pull Request Overview

This PR changes MPU to be an unsafe trait. It is vital for Tock's isolation guarantees, and consumers of this trait must be able to rely on its implementations adhering to the trait's API contract.

It also changes the mock MPU implementation from the unit type () to a newtype NoMPU that itself is unsafe to construct, as doing so (and then using this type as an implementation of MPU) is unsound.

Testing Strategy

N/A

TODO or Help Wanted

N/A

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make prepush.

@github-actions github-actions bot added kernel arch/risc-v RISC-V architecture labels Sep 16, 2025
Comment on lines +83 to +87
/// This is an `unsafe trait`, as it is crucial to uphold Tock's isolation
/// properties, and thus safety of the Tock kernel. Users of this trait must be
/// able to rely on its implementations being correct, and implementing the
/// exact semantics as documented on its associated types and methods.
pub unsafe trait MPU {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious what the specific soundness concern is for the MPU. At some level, every line of code must be correct for Tock to be correct. Any kernel function (and chips, etc.) could just have an unsafe block and do whatever, and the caller has to trust that any function or trait impl in a yes_unsafe crate is implemented correctly.

For STV it more or less makes sense to me because the only purpose of that type is to address Rust safety/soundness issues with static mut. But MPU is quite different.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some level, every line of code must be correct for Tock to be correct.

I think there is a more nuanced distinction around Rust's soundness.

The soundness of Tock's basic kernel and architecture implementations relies on the fact that applications cannot access kernel memory. This is provided by the MPU, which trusted components (i.e., the core kernel) consume an externally provided implementation of (impl MPU), and subsequently use to enforce this isolation.

However, if any arbitrary, semi-trusted component can provide an implementation of the MPU trait, then the core kernel cannot rely on any guarantees and claims made on the implementation of that trait for upholding memory isolation, vital for overall soundness. This is especially true given that the kernel accepts an implementation of this trait, passed in from another crate.

This is different from other correctness concerns. While, for instance, a ring buffer implementation could be incorrect and discard elements, this is not inherently unsound as per Rust's soundness definition. If an incorrect ring buffer implementation means, that, e.g., a userspace application will no longer be scheduled by a round-robin scheduler, that is fine as far as Rust's soundness goes.

Only if the kernel relies on the correctness of that implementation to uphold Rust's strict soundness requirements would a RingBuffer implementation have to stem from the same crate, or the trait would have to be unsafe, or a method accepting an impl RingBuffer (under the assumption that it can rely on it being a correct implementation to prevent undefined behavior) be an unsafe method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The soundness of Tock's basic kernel and architecture implementations relies on the fact that applications cannot access kernel memory.

That should be in the comment.

It sounds like it isn't really that important that the entire implementation is correct, just that it protects kernel memory.

While, for instance, a ring buffer implementation could be incorrect and discard elements, this is not inherently unsound as per Rust's soundness definition.

I was thinking something like ringbuffer needs to store a new element, so it decides to use staticref (or similar) to arbitrarily choose some location in memory it thinks would be a good place to store it, without any concern for if that memory happens to alias with some other Tock kernel object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be in the comment.

Sure, always happy to improve the docs.

I was thinking something like ringbuffer needs to store a new element, so it decides to use staticref (or similar) to arbitrarily choose some location in memory it thinks would be a good place to store it, without any concern for if that memory happens to alias with some other Tock kernel object.

There's a difference between a component being itself unsound, or a component relying on a correct implementation of another component (abstracted through a trait) for the former component's soundness.

We have the latter case here: the kernel relies on the MPU to correctly configure the hardware, for the kernel's own soundness. This case can only be solved by not accepting any arbitrary implementations passed in from other crates (especially from reverse-dependencies), or making the trait itself unsafe, or making the method accepting this trait instance unsafe.

Or, in other words, even a memory- and type-safe MPU implementation can break the kernel's soundness, hence we must use unsafe to place additional semantic restrictions of what implementations of this trait can do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like it isn't really that important that the entire implementation is correct, just that it protects kernel memory.

Disentangling these concerns seems hard to do in practice, but yes, to uphold soundness I believe it is sufficient if an MPU implementation would uphold a subset of its API contract, namely that applications don't have access to kernel-owned memory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, in other words, even a memory- and type-safe MPU implementation can break the kernel's soundness, hence we must use unsafe to place additional semantic restrictions of what implementations of this trait can do.

I think this is the most clear explanation.

I agree with the change in this PR (I'm pretty sure). However, it still seems wrong to me, because the unsafe is in the wrong place. The unsafe operation isn't implementing something wrong. The unsafe operation is using a bad implementation. This PR addresses this even with the NoMPU constructor being unsafe.

What does it mean that the same PR has an implementation which explicitly breaks the unsafe guarantees being added? One way to resolve this I think is that using an unsafe trait is the wrong mechanism. Instead, it should be some new thing akin to capabilities. Sensitive trait implementations should require a newcapability to construct (ie to call new()). Which newcapability depends on the implementation. NoMPU would require an unsafe newcapability (that requires unsafe to construct) called Dangerous or NotForProduction or something. Normal MPU implementations would require either no newcapability or some benign, easy to create newcapability.

However, I'm not sure there is any way to actually do this with what rust provides.

I'm not sure it is ok for us to include NoMPU in the kernel. I agree it is useful. But if we are going to make safety requirements to implement traits, I'm not sure we should immediately break those requirements.

/// in applications having unrestricted access to kernel memory. As such,
/// constructing this type is an `unsafe` operation.
// By having this type contain a private field, we prevent constructing it from
// outside this crate, except through the `unsafe fn new()` constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would someone use NopMPU? Is this for hardware that lacks an MPU?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have previously used it for board bring-up and debugging purposes. It may potentially be used for hardware lacking an MPU, although hopefully the unsafe constructor should make clear that this effectively opts out of any safety and soundness promises that Tock makes otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty default is a legacy from when very-early Tock had some cortex-m0 (not cortex-m0+) boards (specifically nRF51822 IIRC) in-tree, which does not have an MPU.

It remains a useful escape hatch for limited hardware, but should definitely be made harder to use.

@bradjc bradjc changed the title kernel/platform/mpu: make MPU an unsafe trait, change mock impl from () to NopMPU kernel/platform/mpu: make MPU an unsafe trait, change mock impl from () to NoMPU Sep 17, 2025
Comment on lines +286 to +288
/// This type does not meet the safety requirements outlined on the `MPU` trait,
/// hence constructing it is also an unsafe (and unsound) operation.
unsafe impl MPU for NopMPU {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned that we cannot do this. I agree that making the constructor unsafe is a good companion, but I don't think it is valid to blatantly ignore the safety requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants