Skip to content

cmse: clear padding when crossing the secure boundary#157397

Merged
rust-bors[bot] merged 4 commits into
rust-lang:mainfrom
folkertdev:cmse-clear-padding
Jun 26, 2026
Merged

cmse: clear padding when crossing the secure boundary#157397
rust-bors[bot] merged 4 commits into
rust-lang:mainfrom
folkertdev:cmse-clear-padding

Conversation

@folkertdev

@folkertdev folkertdev commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

View all comments

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).

cc @Jules-Bertholet
r? @davidtwco

@folkertdev folkertdev added F-cmse_nonsecure_entry `#![feature(cmse_nonsecure_entry)]` F-abi_cmse_nonsecure_call `#![feature(abi_cmse_nonsecure_call)]` labels Jun 3, 2026
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jun 3, 2026
Comment thread compiler/rustc_abi/src/layout/ty.rs Outdated
Comment on lines +364 to +370
// FIXME: dedup with the one in
// `compiler/rustc_const_eval/src/interpret/validity.rs`
/// Represents a set of `Size` values as a sorted list of ranges.
// These are (offset, length) pairs, and they are sorted and mutually disjoint,
// and never adjacent (i.e. there's always a gap between two of them).
#[derive(Debug, Clone)]
struct RangeSet(Vec<(Size, Size)>);

@folkertdev folkertdev Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@RalfJung given the dependency graph, do you have objections to moving the rustc_const_eval into rustc_abi?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

moving the rustc_const_eval into rustc_abi?

... you want to move the entire crate into rustc_abi? I doubt that's what you mean. I am confused.^^

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you mean moving this data structure -- if it gets moved it should move to rustc_data_structures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It can't due to the use of the Size type which is defined in rustc_abi.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It should probably be generic over some traits then.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I've found something that kind of works, but rustc_const_eval uses a bunch of implementation details.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

rustc_const_eval uses a bunch of implementation details.

Is that why the field is pub?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, in particular the const eval code breaks the invariant of the data structure by adding an empty range to make some other logic work out.

Comment thread compiler/rustc_codegen_ssa/src/mir/block.rs Outdated
Comment thread compiler/rustc_abi/src/layout/ty.rs Outdated
/// Ranges of bytes that are initialized for some valid value of this type. In particular for
/// enums and unions there are offsets that are initialized for some variants but not for
/// others.
fn add_data_ranges<C>(self, cx: &C, base_offset: Size, out: &mut RangeSet)

@RalfJung RalfJung Jun 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This sounds very strange. You can't just zero all bytes that would be allowed to be uninit -- that can introduce UB as it might zero something where the active enum variant says it must be non-zero.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe the text is not clear, but this function finds the union of all fields, not the intersection.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Specifically for e.g.

// size_of::<X>() == 8
#[repr(C, align(4))]
enum X {
    A(u8),
    B(u16),
}

I believe the discriminant is 32 bits here, then one variant uses 1 byte, the other 2, and this function will count the first 6 bytes as data bytes. The remaining 2 bytes are padding and should be zeroed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see, makes sense.

Well, kinda. Obviously the enum might be in variant A and then there's still a byte of junk being passed around here. So I hope you don't intend for this to be used for anything critical.

The entire concept of "clearing padding for security" sounds pretty backwards to me. Similar to these crates that try to "erase" secrets from memory. It's trying to shove a square into a round hole.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, kinda. Obviously the enum might be in variant A and then there's still a byte of junk being passed around here. So I hope you don't intend for this to be used for anything critical.

The lint in #147697 will warn on this sort of case. That PR will need some updating though after this one is merged.

@RalfJung RalfJung Jun 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That entire approach feels like piling hacks on top of hacks. I guess that's what we have to do if we want to copy what C does.

@rust-log-analyzer

This comment has been minimized.

@folkertdev folkertdev force-pushed the cmse-clear-padding branch from cb1d784 to 3be4bee Compare June 3, 2026 20:16
@rustbot

rustbot commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Comment thread compiler/rustc_data_structures/src/range_set.rs Outdated
@folkertdev folkertdev force-pushed the cmse-clear-padding branch from 3be4bee to a037297 Compare June 4, 2026 20:55

@davidtwco davidtwco left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

r=me if @RalfJung is happy their comments are resolved

View changes since this review

@folkertdev

Copy link
Copy Markdown
Contributor Author

In that case,

r? RalfJung

@rustbot rustbot assigned RalfJung and unassigned davidtwco Jun 14, 2026
@rustbot

rustbot commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

RalfJung is not on the review rotation at the moment.
They may take a while to respond.

@RalfJung RalfJung left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

r=me with some comment nits

View changes since this review

Comment thread compiler/rustc_abi/src/layout/ty.rs Outdated
Comment thread compiler/rustc_abi/src/layout/ty.rs
Comment thread compiler/rustc_abi/src/layout/ty.rs Outdated

if self.fn_abi.conv == CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) {
// The return value of an `extern "cmse-nonsecure-entry"` function crosses the secure
// boundary. Zero padding bytes so information does not leak.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should at least acknowledge that value-dependent padding is ignored, and reference somewhere that explains why that is okay.

);

// The arguments of an `extern "cmse-nonsecure-call"` function cross the secure
// boundary. Zero padding bytes so information does not leak.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should at least acknowledge that value-dependent padding is ignored, and reference somewhere that explains why that is okay.

@folkertdev folkertdev force-pushed the cmse-clear-padding branch from 25dc638 to 5f34c50 Compare June 25, 2026 11:24
@rustbot

rustbot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

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.

@folkertdev

Copy link
Copy Markdown
Contributor Author

@bors r=davidtwco,RalfJung

@rust-bors

rust-bors Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 5f34c50 has been approved by davidtwco,RalfJung

It is now in the queue for this repository.

🌲 The tree is currently closed for pull requests below priority 6. This pull request will be tested once the tree is reopened.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 25, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 25, 2026
…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
rust-bors Bot pushed a commit that referenced this pull request Jun 25, 2026
Rollup of 10 pull requests

Successful merges:

 - #158410 (Update LLVM for Mach-O __LINKEDIT alignment fix.)
 - #157397 (cmse: clear padding when crossing the secure boundary)
 - #158036 (Add -Zinstrument-mcount=fentry to -Zinstrument-mcount)
 - #158330 (llvm: use intrinsics for f16, f32 minimum/maximum)
 - #158359 (fix(tests): allow either branch direction in ilog_known_base)
 - #158067 (LLVM 23: Adapt codegen test to moved assume)
 - #158261 (Move part of the target checking for `#[may_dangle]` to the parser)
 - #158358 (Fix invalid E0609 raw pointer deref suggestion inside macros)
 - #158392 (delegation: add tests for defaults and infers in generics)
 - #158394 (Generate synthetic generic args only for delegation's child segment)
@rust-bors rust-bors Bot merged commit 953dec1 into rust-lang:main Jun 26, 2026
13 checks passed
@rustbot rustbot added this to the 1.98.0 milestone Jun 26, 2026
rust-timer added a commit that referenced this pull request Jun 26, 2026
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
@RalfJung

RalfJung commented Jun 26, 2026

Copy link
Copy Markdown
Member

Btw, is this an opsem guarantee of some sort? Does it also affect Rust-to-Rust calls and hence needs an implementation in Miri?

@folkertdev

Copy link
Copy Markdown
Contributor Author

I'm not sure when something qualifies as an opsem guarantee.

The padding being cleared is something that users should be able to rely on, and it will be part of the reference. That the bytes are zeroed specifically is an implementation detail (idk we could pick 0xAA too if that had advantages). The value of the padding byte is specified to be non-deterministic (in practice it's always 0x00 but that's not guaranteed).

The clearing is only important though when the secure boundary is actually crossed, and that is impossible to do within a single program.

So practically an implementation in Miri doesn't have much use, I don't think there is any new UB that should be caught. I may be missing something though.

@RalfJung

Copy link
Copy Markdown
Member

Thinking about it again, I think the change you are making here is impossible to observe in a UB-free program. Padding is reset to uninit when an argument is passed, so any program trying to check if there's a 0 there or not has UB.

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

Labels

F-abi_cmse_nonsecure_call `#![feature(abi_cmse_nonsecure_call)]` F-cmse_nonsecure_entry `#![feature(cmse_nonsecure_entry)]` S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants