cmse: lint on unions crossing the secure boundary#147697
Conversation
This comment has been minimized.
This comment has been minimized.
a344d30 to
514010e
Compare
This comment has been minimized.
This comment has been minimized.
514010e to
9d276b5
Compare
This comment has been minimized.
This comment has been minimized.
9d276b5 to
4a269f5
Compare
This comment has been minimized.
This comment has been minimized.
4a269f5 to
0b424f6
Compare
|
r? @davidtwco This seems useful just for parity with clang. The code is built to be extended to cover more cases of types possibly containing uninitialized memory, but by the looks of things there isn't currently a straightforward way to detect such types (cc #t-compiler/help > check whether a type can be (partially) uninitialized) |
| use minicore::*; | ||
|
|
||
| #[repr(Rust)] | ||
| pub union ReprRustUnionU64 { |
There was a problem hiding this comment.
Can you add cases where the unions are contained within other types to these tests?
0b424f6 to
4586300
Compare
There was a problem hiding this comment.
The lint will only fire when the cmse ABIs are enabled, but I suppose the name does sort of "leak".
the OP here provides some context. The bigger picture is in this draft RFC that I plan to formally submit soon.
| warning: passing a union across the security boundary may leak information | ||
| --> $DIR/return-uninitialized.rs:46:5 | ||
| | | ||
| LL | / match 0 { | ||
| LL | | | ||
| LL | | 0 => Wrapper(ReprRustUnionU64 { _unused: 1 }), | ||
| LL | | _ => Wrapper(ReprRustUnionU64 { _unused: 2 }), | ||
| LL | | } | ||
| | |_____^ | ||
| | |
There was a problem hiding this comment.
would it make sense to warn in the individual arms of the match instead? I think as a user that would be better in this simple case, though I don't know that we can make that robust (e.g. thinking about labeled blocks).
Would that also include padding? In any case, this seems wildly useful for many users, not just On that basis I'm wondering if we should aspirationally call this Is there some means by which we could allow the user to suppress this not by |
Ideally, yes. But I don't have a good strategy for actually achieving that. Based on #t-compiler/help > check whether a type can be (partially) uninitialized @ 💬 maybe there are parts of safe transmute that are helpful here.
I'm not familiar enough with the opsem details here, but in any case I don't think we'd want to tie our lints to LLVM implementation details that much? |
|
I'm not proposing to depend on LLVM details. I was asking how we can handle code that's doing the correct thing (whatever that might be), such as ensuring the uninitialized memory is zero. In C, you would ensure the union is zero-initialized, then initialize the correct field, then return it. What's the equivalent operation that you can do in Rust, and can we ensure that we don't emit the lint if you properly do that? |
|
What I had in mind is to use At least in that case, I don't think we have a good way of checking whether the value is properly initialized without actually evaluating the program (e.g. with miri). |
|
(Repeating my comment on the RFC: rust-lang/rfcs#3884 (comment)) Rust doesn't preserve padding on typed copies (and, IIRC, nor does C). Any padding in a value passed to a function call can just be zeroed. There is no need to lint if this is implemented properly (presumably by the backend?). |
5abb0ff to
9e50dc6
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
… r=WaffleLapkin cmse: test returning `MaybeUninit<T>` tracking issue: rust-lang#81391 tracking issue: rust-lang#75835 Some tests from rust-lang#147697 that already work and are useful. Extracting them shrinks that (currently blocked) PR. The code in `tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs` checks that `MaybeUninit<T>` is considered abi-compatible with `T`. The code in `tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs` really only tests that no errors/warnings are emitted. r? davidtwco
Rollup merge of #155522 - folkertdev:cmse-test-maybe-uninit, r=WaffleLapkin cmse: test returning `MaybeUninit<T>` tracking issue: #81391 tracking issue: #75835 Some tests from #147697 that already work and are useful. Extracting them shrinks that (currently blocked) PR. The code in `tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs` checks that `MaybeUninit<T>` is considered abi-compatible with `T`. The code in `tests/ui/cmse-nonsecure/cmse-nonsecure-entry/params-via-stack.rs` really only tests that no errors/warnings are emitted. r? davidtwco
This comment has been minimized.
This comment has been minimized.
9e50dc6 to
d524a62
Compare
This comment has been minimized.
This comment has been minimized.
|
This is still waiting for #157397, but I've updated the lint to now find value-dependent padding (so, bytes that are padding only for some, but not all, valid values of the type). |
This comment has been minimized.
This comment has been minimized.
…avidtwco,RalfJung cmse: clear padding when crossing the secure boundary tracking issue: rust-lang#81391 tracking issue: rust-lang#75835 RFC: rust-lang/rfcs#3884 related: rust-lang#147697 quick context: cmse creates a distinction between code running in secure mode and non-secure mode (think kernel space versus user space). Secure mode has access to data (e.g. encryption keys) that must not leak to non-secure mode. They use a special calling convention that clears unused registers, but padding in arguments/return values can contain stale secure data. This PR clears the padding bytes (and similar, e.g. space not used in any variant of a union/enum) when values are passed over the secure boundary. Separately we'll have a lint to warn on enums and unions being passed across the boundary: for them we can't statically know whether the variant that is passed contains padding. This is conceptually modeled after a similar feature in `clang` ([implementation](https://github.com/llvm/llvm-project/blob/065a39b9f7f06fca0926394096ee1c1fac41d446/clang/lib/CodeGen/CGCall.cpp#L4041-L4087)). cc @Jules-Bertholet r? @davidtwco
…avidtwco,RalfJung cmse: clear padding when crossing the secure boundary tracking issue: rust-lang#81391 tracking issue: rust-lang#75835 RFC: rust-lang/rfcs#3884 related: rust-lang#147697 quick context: cmse creates a distinction between code running in secure mode and non-secure mode (think kernel space versus user space). Secure mode has access to data (e.g. encryption keys) that must not leak to non-secure mode. They use a special calling convention that clears unused registers, but padding in arguments/return values can contain stale secure data. This PR clears the padding bytes (and similar, e.g. space not used in any variant of a union/enum) when values are passed over the secure boundary. Separately we'll have a lint to warn on enums and unions being passed across the boundary: for them we can't statically know whether the variant that is passed contains padding. This is conceptually modeled after a similar feature in `clang` ([implementation](https://github.com/llvm/llvm-project/blob/065a39b9f7f06fca0926394096ee1c1fac41d446/clang/lib/CodeGen/CGCall.cpp#L4041-L4087)). cc @Jules-Bertholet r? @davidtwco
Rollup merge of #157397 - folkertdev:cmse-clear-padding, r=davidtwco,RalfJung cmse: clear padding when crossing the secure boundary tracking issue: #81391 tracking issue: #75835 RFC: rust-lang/rfcs#3884 related: #147697 quick context: cmse creates a distinction between code running in secure mode and non-secure mode (think kernel space versus user space). Secure mode has access to data (e.g. encryption keys) that must not leak to non-secure mode. They use a special calling convention that clears unused registers, but padding in arguments/return values can contain stale secure data. This PR clears the padding bytes (and similar, e.g. space not used in any variant of a union/enum) when values are passed over the secure boundary. Separately we'll have a lint to warn on enums and unions being passed across the boundary: for them we can't statically know whether the variant that is passed contains padding. This is conceptually modeled after a similar feature in `clang` ([implementation](https://github.com/llvm/llvm-project/blob/065a39b9f7f06fca0926394096ee1c1fac41d446/clang/lib/CodeGen/CGCall.cpp#L4041-L4087)). cc @Jules-Bertholet r? @davidtwco
…boundary When a union passes from secure to non-secure (so, passed as an argument to an nonsecure call, or returned by a nonsecure entry), warn that there may be secure information lingering in the unused or uninitialized parts of a union value. https://godbolt.org/z/vq9xnrnEs
Any guaranteed padding (bytes that are padding regardless of the value of the type) are now zeroed, so we only need to lint on enums and unions where some offsets are padding for some variants, but not for others.
d524a62 to
f9be9c7
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
| Variants::Multiple { variants, .. } => { | ||
| // A byte is data for every value only when it is data for every value of every | ||
| // variant. | ||
| out = variants | ||
| .indices() | ||
| .map(|variant| self.for_variant(cx, variant)) | ||
| .map(|variant| variant.always_data_ranges(cx, base_offset)) | ||
| .reduce(|acc, f| acc.intersection(&f)) | ||
| .unwrap_or(out); | ||
| } |
There was a problem hiding this comment.
I think this does not properly account for the discriminant.
Also someone familiar with our async lowering should ensure that this behaves correctly for generators/coroutines,
There was a problem hiding this comment.
You're right, and the docs here are all a bit confusing. Anyhow, I think just also iterating over the fields is sufficient. At least it mentions some details about coroutines.
There was a problem hiding this comment.
Yeah I think it should be something like
- intersect all the variants
- then union that with all the fields
7b92511 to
2f650aa
Compare
| continue; | ||
| }; | ||
|
|
||
| if !layout.variant_dependent_padding_ranges(cx).is_empty() { |
There was a problem hiding this comment.
This seems... potentially problematic. Whether we emit this lint now depends on incredibly unstable details of the layout algorithm. Is that really what we want?
What I expected is that we just lint about every union and enum, to avoid having the lint suddenly pop up when we change the layout of enums. Any code that relies on "yes this is an enum but there's no value-dependent padding" is relying on layout details we don't guarantee, and thus IMO fundamentally broken.
There was a problem hiding this comment.
Well this calling convention is a variant of the C calling convention, used for FFI, so user should be using a stable layout, likely repr(C). In that case the layout is perfectly stable.
I want the lint to actually be used, and for that it helps to have few false positives. So I expect this to work well in practice, even though it is inelegant in theory.
There was a problem hiding this comment.
It's not about elegance, it's about our ability to continue evolving the layout algorithm. It's about Hyrum's law.
|
☔ The latest upstream changes (presumably #157575) made this pull request unmergeable. Please resolve the merge conflicts by rebasing. |
View all comments
tracking issue: #81391
tracking issue: #75835
When a union passes from secure to non-secure (so, passed as an argument to a non-secure call, or returned by a non-secure entry), warn that there may be secure information lingering in the unused or uninitialized parts of a union value.
This lint matches the behavior of clang (see https://godbolt.org/z/vq9xnrnEs). Like clang we warn at the use site, so that individual uses could be annotated with
#[allow(cmse_uninitialized_leak)].Ideally we'd warn on any type that may have uninitialized parts, but I haven't figured out a good way to do that yet.
It is still unclear whether a union value where all fields are equally large and allow the same bit patterns can be considered initialized (see rust-lang/unsafe-code-guidelines#438), so for now we just warn on any
union.r? @ghost