-
Notifications
You must be signed in to change notification settings - Fork 705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add CapbilityPtr
and Add SuccessAddr
and SuccessPtr
syscall variants
#4174
Conversation
This changes some u32s into usizes as appropriate. It also introduces a new MetaPtr type that is a pointer that has explicit target dependant metadata. On many platforms, this will just be a wrapper around usize. On CHERI platforms MetaPtr will be a capability. See later commit. It also adds a new syscall encoding `encode_syscall_return_metaptr` That is intended to work for both 32/64 platforms with or without this extra metadata. Change-Id: I40faa11c1fd53debc6e9b21d00772660cacf8cab
This PR seems to have broken the litex-sim-ci, and I suspect that is a true breakage, and the app is not running. Have you run any risc-v apps in qemu during your testing? |
What about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally good. I think it needs more explanation and potentially consistency in what a MetaPtr
's purpose is, especially without invoking the specific CHERI use-case, since it's something to be used regardless of CHERI (or other capability) hardware being available.
kernel/src/metaptr.rs
Outdated
Execute, | ||
} | ||
|
||
impl From<MetaPtr> for usize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is OK because a MetaPtr
is always convertible into a raw pointer by just losing any meta information. However, might be better to implement this for <T> *const T
(which can always be cast to a usize
anyway)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add that too (it might even exist as a method?), but this one gets used a lot in syscall / return argument handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah got it. Does this imply that those arguments or returns should actually be *const T
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its happening a lot because values that are usize, or even eventually u32, are being passed in registers that can hold something as wide as MetaPtr
(the widest type, or so I have assumed).
@bradjc has suggested that maybe those should be a separate RegisterData
type, something large enough to hold u32
/isize
/*const()
/MetaPtr
.
I suppose, fairly, one could imagine a platform with pointers that are smaller than u32, in which case really RegisterData would wrap around the largest type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documenting that CapabilityPtr
is a pointer then using it to store other things is very confusing, and I think having a separate RegisterData
[1] type would help that confusion. CapabilityPtr
is already a confusing enough type to understand, especially for contributors who are unfamiliar with CHERI.
The fact that some CHERI systems use different registers for integers and capabilities however, does throw a wrench into the works. However, there are two obvious ways that come to mind for how we might choose to handle that at the ABI level:
- We can pass integers in the integer registers and capabilities in the capability registers. In this case, the kernel knows which are which so we can can use
usize
for the integer registers andCapabilityPtr
for the capability registers [2]. - We can pass everything in the capability registers and ignore the integer registers. This is just treating the system as a CHERI system that uses the same registers for everything, and we'd just use
RegisterData
for all the register values.
In both cases, it makes sense to only use CapabilityPtr
for pointer types, so I don't think it's really a blocker for having both CapabilityPtr
and RegisterData
. Option 1 would be somewhat awkward, though, because we would only use RegisterData
for the capability registers.
[1] libtock-rs just calls this Register
[2] This ignores the possibility of non-pointer capabilities, which is another complication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not convinced RegisterData
is really a single type. Machines have different width registers. To say that RegisterData
should refer to just one of them is odd to me. If we did introduce a RegisterData
here, I would expect it to wrap CapabilityPtr
(with an appropriate comment saying the type is being used because it is expected to be widest). It would certainly throw a spanner if ever a different ABI were introduced that actually used different width registers, to have a RegisterData
and then SomeOtherRegisterData
. But I am happy to add RegisterData
if that is the consensus and makes the syscall layer easier to understand.
- is/was the ABI for those split file machines. In a similar fashion to how you would select float/integer registers on platforms that split them.
kernel/src/syscall.rs
Outdated
/// The obvious extension of TRD104 that works for 32-bit and 64-bit platforms. | ||
/// This makes no changes to TRD104 on 32-bit platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually I think we will benefit from a dedicated PR / discussion thread updating TRD104 and more explicitly discussing the best ABI for capabilities.
I'm not sure we should necessarily block this PR on that longer process.. but this code does change our ABI, which feels like it should be partitioned off somehow more explicitly from anything not using CHERI in the short term; I'm not sure the least-painful way to do that :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am doing to take a stab at TRD104 now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only looked at syscall.rs
for now. I agree with @ppannuto that this would benefit from an update to TRD 104 and feel rather strongly that this update should be merged before or in tandem with this PR. It's hard to fully reason about these changes and what they'll mean for new platforms and a more abstract update of the TRD will make that easier to reason about.
I want to cross-post my comment from tock/book#53 here:
Does this PR depend on command arguments being |
If we ever refactored to support strict provenance or a cheri purecap rustc, *const() is a far better choice than usize. Change-Id: I56947d53dc2f2ef9d1d5ac80641bc4410f383813
Change-Id: I8049bb48e48a162feb261cb4e1e15f7e5b4d8f6e
Make return variants that contain pointers and usizes explict about this type information. A backwards compatability mapping is also provided for legacy u32 platforms. Change-Id: Ied5513c58bfa87be3599ba366692f66af3da1691
Change-Id: Iccb152457179f1a48eb110c4e4eb7c2efc19150d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm marking approved because I think this is very close, and want to communicate my approval vote.
There are a few nits in the new doc.
I also think we should rename MetaPtr
to something more descriptive, e.g. AuthenticPtr
as @bradjc suggested. Or something else. Capability wouldn't be bad if it didn't imply an incorrectly high level of assurance for non-tagged architectures. Meta
is just vague.
Co-authored-by: Amit Levy <[email protected]>
Change-Id: I0b59feb2d1d6f60be4aaa46adc430785651837ca
Change-Id: Iccd7ff9f675786cf04abfa16ce89e8c3667a756d
kernel/src/capability_ptr.rs
Outdated
use core::fmt::{Formatter, LowerHex, UpperHex}; | ||
use core::ops::AddAssign; | ||
|
||
/// A pointer with target specific metadata concerning validity or access rights. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me how we could actually use this type in upstream Tock since we don't have hardware that supports this metadata.
I still think we need an abstraction which captures the intent and not the implementation. This is tied to the naming question, but is distinct. It may actually make sense to call this struct CapabilityPtr
given this comment description. However, I think something like QualifiedPtr
better captures the distinction between the type of pointer considered in this PR, and other types of pointers. A QualifiedPtr
would be described entirely based on its use case within Tock, and not at all based on how it could/is implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't expect this type to add much value over *const ()
in upstream Tock, as I think it only makes sense on the platforms that have such a thing, so I don't think it will have any added benefit per se.
However, I think Capability does carry some semantic abstraction without just being a CHERI capability. For example, if Tock ever targeted a platform with PAC (which I would frame as password capabilities), I could imagine the checked parts of this interface checking pointer integrity, casts from usize signing the pointer etc.
I think that "authority bearing" does convey what it is used for and where it should be used and is independent of implementation details, such as how that authority is encoded or checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is some talking past each other here.
I don't think @bradjc is looking for "value" in non-CHERI Tock, but rather an explanation/name/whatever of the abstraction (the thing called CapabilityPtr
) that makes it clear how it should be used. E.g., it is describing some concept. That concept is enforced if there are hardware capabilities. It's not enforced if there are not. But it's still meaningful to use that abstraction elsewhere, if only as a description of the meaning of some particular value.
My working model is something like: a CapabilityPtr
names some address in userspace (though not necessarily currently/always accessible to userspace) that, conceptually, userspace may or may not be permitted to read, write, or execute. There are certain operations on this abstraction, and those operations carry some implicit (enforced or otherwise) affect on those permissions. For example, I can add/subtract offsets from a CapabilityPtr
, and the result may or may not have the same access permissions (e.g. the permissions will drop if the offset puts me out of bounds of the region it referred to). I can also extract the word-size address component as pure data (a usize
), which let's me "hide" data inside a CapabilityPtr
in the same way I can "hide" data in a *const _
.
Again, these properties may or may not be enforced. In this sense it's kind of like the difference between a bounds-checked slice in Rust and an array in C with no bounds checking. In all cases, correct programs will behave the same, and will use these in accordance with the rules/properties. On CHERI, an incorrect program will fault if it uses it incorrectly. Similarly, a Tock kernel that uses this concept correctly in the right places will enable correct userspace programs regardless, but a Tock kernel that uses it incorrectly will only fail to support programs under CHERI (because in non-CHERI userspace programs can also de-reference a usize
/*const _
so the type that's passed up from the kernel doesn't actually matter).
OK, this concept now tells us where/how to use a CapabilityPtr
. For example, if a system call is intending to return a value that might mean "a pointer userspace may dereference in some way" it should be a CapabilityPtr
(e.g. the user-data passed down in the allow system call and up in upcalls). If a value is passed between userspace and the kernel that should never be treated as a pointer userspace can dereference, it should not be a CapabilityPtr
.
(perhaps I'm wrong about the specific explanation of the abstraction, but hopefully this at least captures the gap in understanding)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think about this abstraction in a similar way. Maybe then, I am not sure what the suggestion for change is here. Just that Capability
is still not a good name for this thing? Or that the text could do a better job explaining it use? Or its meaning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or that the text could do a better job explaining it use? Or its meaning?
@bradjc, confirm?
I think the clarifying the meaning is necessary to review whether the rest of the PR is indeed using it in an appropriate way, etc. Especially to the extent it concerns things like changing TRD104 and related core kernel interfaces.
If that's the case, I can also take a stab at writing this out. I can see how the comment explaining this below is maybe too CHERI and implementation oriented
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I think Capability does carry some semantic abstraction without just being a CHERI capability. For example, if Tock ever targeted a platform with PAC (which I would frame as password capabilities), I could imagine the checked parts of this interface checking pointer integrity, casts from usize signing the pointer etc.
I think one of the questions I have is, in this scenario, would PAC use the same CapabilityPtr
as-defined here and the same syscall interface to pass the capability pointer, etc?
More important is up-leveling from that specific question and away from something specific to CHERI or to PAC or to [thing]—is what's proposed here a general mechanism for conveying pointers with metadata? If so, how do we express what the properties of a "pointer with metadata" abstraction should be?
A related question based on @alevy's new text, is this only for userspace pointers? Are theses capabilities only enforced during userspace operation, or are there cases where we would want to use hardware capability enforcement to e.g. support stronger isolation / IFT / etc between resources passed to capsules? Not to re-bikeshed the name, but if it's really userspace-only, that feels like it should be a front a center piece of the naming/rationale.
I'm also struggling with the repeated assertion that this is only useful on platforms with hardware capabilities. I feel like that isn't the case? i.e., a non-C userspace with some kind of managed runtime could in principle [less-efficient, sure] enforce all the same properties as CHERI or equivalent hardware. Perhaps explaining the pointer abstraction here with the perspective of also answering "what would a pure-software implementation that intercepts every memory access provide" would help, as it would divorce the discussion from any particular implementation and invite thinking of things in terms of the more fundamental properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would PAC use the same CapabilityPtr as-defined here and the same syscall interface to pass the capability pointer, etc
Hopefully. It may have to grow to assume a superset of hardware checked integrity (CHERI motivated bounds and W,R,X, but one can imagine more).
A related question based on @alevy's new text, is this only for userspace pointers?
No, I have left a comment there. The boundary is just the lowest hanging fruit because it is the boundary where compiler enforcement stops applying and we must rely on hardware.
i.e., a non-C userspace with some kind of managed runtime could in principle [less-efficient, sure] enforce all the same properties as CHERI or equivalent hardware
Well, I think it does, and I think that language is Rust
and that is already precisely what libtock-rs does. I would argue that, in the rust type system, &T
is already a capability enforced by the type system, and libtock-rs enforces that you make syscalls only when you have one of those. The point of CapabilityPtr
was to be a source for authority beyond the existing type system (which is why it immediately found use across compiler boundaries). We cannot proves you used libtock-rs properly. Use of CapabilityPtr
can fix issues in even Rust: when either the compiler is wrong, or unsafe Rust invokes UB. Unsafe Rust can fabricate &T
. In userspace or the kernel. On some platforms (namely CHERI), it cannot fabricate a valid CapabilityPtr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is what's proposed here a general mechanism for conveying pointers with metadata?
@ppannuto I intentionally avoided defining this in terms of metadata. It has a semantic meaning relating to user references. On some platforms thateaning might be enforcible with hardware support, on other it won't.
The important bit is that this type is a (valid or invalid) userspace reference. That there may be metadata on some platforms is incidental.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does PAC stand for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pointer Authentication Code: https://learn.arm.com/learning-paths/servers-and-cloud-computing/pac/pac/
After extensive discussion with @LawrenceEsswood, the only change since @bradjc's approving review is that memop 6 now returns a |
I'd suggest merging #4229 first, and then rebasing this prior to merge. |
Merged master with #4229 included. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, we did end up with both syscall pointer and address! I think that is the right solution. I think it's important that we make it clear that it does not hold just any usize
, and scoping it to a memory address does that.
I'll keep my comment about adding back the "ess" part for the next PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking forward, I would like to prioritize splitting out OpaqueRegisterContents
(my probably-needlessly-wordier version of bradjc's Register
) from CapabilityPtr
.
I wonder if the right approach there is perhaps not a type alias but an union Register
with Opaque
and CapabilityPtr
variants (perhaps as well as u32
or even u8
etc) — i.e., the union Register
is used in syscall boundary code to indicate that things must fit in a register, but the variant inside informs how (or if, e.g. Opaque
appdata
) the kernel should interpret the register contents.
There's also some unresolved name bikeshed'ing (or bounding / explanation of type intent), e.g. I struggle a lot with the name here suggesting generic for pointer to anywhere, but the use case restricting only to userspace.
// A [
CapabilityPtr
] points to memory a userspace process may be
/// permitted to read, write, or execute.
CapbilityPtr
and Add SuccessAddr
and SuccessPtr
syscall variants
We did it everyone! 🎉🎶🏞️⭐💥🌈☄️🎂🎊🎖️ |
Pull Request Overview
This changes some u32s into usizes as appropriate.
It also introduces a new MetaPtr type that is a pointer that has explicit target dependant metadata. On many platforms, this will just be a wrapper around usize. On CHERI platforms MetaPtr will be a capability.
See later commit.
It also adds a new syscall encoding
encode_syscall_return_metaptr
That is intended to work for both 32/64 platforms with or without this extra metadata.Testing Strategy
Running on QEMU
TODO or Help Wanted
This pull request still needs review, especially around
encode_syscall_return_mptr
which makes some decisions on how 64-bit syscalls should workDocumentation Updated
None
Formatting
make prepush
.