Skip to content

Commit 5c4e542

Browse files
committed
overhaul structure
1 parent 7ceff6b commit 5c4e542

File tree

1 file changed

+99
-68
lines changed

1 file changed

+99
-68
lines changed

text/0000-cmse-calling-conventions.md

Lines changed: 99 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,123 @@
88

99
Support the `cmse-nonsecure-entry` and `cmse-nonsecure-call` calling conventions.
1010

11-
1211
# Motivation
1312
[motivation]: #motivation
1413

15-
Rust and Trustzone form an excellent pairing for developing embedded projects that are secure and robust.
16-
14+
Rust and Trustzone form an excellent pairing for developing embedded projects that are secure and robust.
1715

1816
# Guide-level explanation
1917
[guide-level-explanation]: #guide-level-explanation
2018

21-
The cmse calling conventions are part of the *Cortex-M Security Extension* that are available on thumbv8 systems. They are used together with Trustzone (hardware isolation) to create more secure embedded applications.
19+
The cmse calling conventions are part of the *Cortex-M Security Extension* that are available on thumbv8 systems. They are used together with Trustzone (hardware isolation) to create more secure embedded applications. Arm defines the toolchain requirements in [ARMv8-M Security Extensions: Requirements on Development Tools - Engineering Specification](https://developer.arm.com/documentation/ecm0359818/latest/), but of course this specification needs to be interpreted in a rust context.
2220

2321
The main idea of Trustzone is to split an embedded application into two executables. The secure executable has access to secrets (e.g. encryption keys), and must be careful not to leak those secrets. The non-secure executable cannot access these secrets, and hence a whole class of security issues is simply impossible in the non-secure app.
2422

2523
The cmse calling conventions facilitate interactions between the secure and non-secure executables. To ensure that secrets do not leak, these calling conventions impose some custom restrictions on top of the system's standard AAPCS calling convention.
2624

27-
The `cmse-nonsecure-entry` calling convention is used in the secure executable to define entry points that the non-secure executable can call. The use of this calling convention hooks into the tooling (LLVM and the linker) to generate an import library (an object file with only declarations, not actual instructions).
25+
The `cmse-nonsecure-entry` calling convention is used in the secure executable to define entry points that the non-secure executable can call. The use of this calling convention hooks into the tooling (LLVM and the linker) to generate a shim that switches the security mode, and an import library (an object file with only declarations, not actual instructions) that can be linked into the non-secure executable.
2826

2927
The `cmse-nonsecure-call` calling convention is used in the other direction, when the secure executable wants to call into the non-secure executable. This calling convention can only occur on function pointers, not on definitions or extern blocks. The secure executable can acquire a non-secure function pointer via shared memory or a non-secure callback can be passed to an entry function.
3028

31-
Both calling conventions are based on the platform's C calling convention, but will not use the stack to pass arguments or the return value. In practice that means that the arguments must fit in the 4 available argument registers, and the return value must fit in a single 32-bit register, or be (a transparently wrapped) 64-bit integer or float. The compiler checks that the signature is valid.
29+
Both calling conventions are based on the platform's C calling convention, but will not use the stack to pass arguments or the return value. In practice that means that the arguments must fit in the 4 available argument registers, and the return value must fit in a single 32-bit register, or be abi-compatible with a 64-bit integer or float. The compiler checks that the signature is valid.
3230
# Reference-level explanation
3331
[reference-level-explanation]: #reference-level-explanation
32+
## ABI Details
33+
34+
The foundation of the cmse ABIs is the platform's standard AAPCS calling convention.
35+
36+
The `cmse-nonsecure-call` and `cmse-nonsecure-entry` ABIs are only accepted on `thumbv8m` targets. On all other targets their use emits an invalid ABI error.
37+
38+
The `cmse-nonsecure-call` ABI can only be used on function pointers. Using it in for a function definition or extern block emits an error. It is sound to cast such a function to `extern "C"`, but calling the function will cause a HardFault. Casting an `extern "C"` function pointer to a `cmse-nonsecure-call` is valid, but will cause a HardFault if the function's definition is not in non-secure memory.
39+
40+
The `cmse-nonsecure-entry` ABI is allowed on function definitions, extern blocks and function pointers. It is sound and valid (in some cases even encouraged) to cast such a function to `extern "C"`. Calling the function is valid and will behave as expected. Casting an `extern "C"` function pointer to `cmse-nonsecure-entry` is valid, but will not change the security mode.
41+
### Argument passing
42+
43+
The main technical limitation over AAPCS is that the cmse ABIs cannot use the stack for passing function arguments or return values. That leaves only the 4 registers to pass arguments, and only supports 1 register worth of return value, unless the return type is ABI-compatible with a 64-bit scalar, which is supported.
44+
45+
```rust
46+
// Valid
47+
type T0 = extern "cmse-nonsecure-call" fn(_: i32, _: i32, _: i32, _: i32) -> i32;
48+
type T1 = extern "cmse-nonsecure-call" fn(_: i64, _: i64) -> i64;
49+
50+
#[repr(transparent)] struct U64(u64);
51+
type T3 = extern "cmse-nonsecure-call" fn() -> U64;
52+
53+
// Invalid: too many argument registers used
54+
type T1 = extern "cmse-nonsecure-call" fn(_: i64, _: u8, _: u8, _: u8) -> i64;
55+
56+
// Invalid: return type too large
57+
type T1 = extern "cmse-nonsecure-call" fn() -> i128;
58+
59+
// Invalid: return type does not fit in one register, and is not abi-compatible with a 64-bit scalar
60+
#[repr(C)] struct I64(i64);
61+
type T2 = extern "cmse-nonsecure-call" fn(_: i64, _: i64) -> i64;
62+
```
63+
64+
An error is emitted when the program contains a signature that violates the calling convention's constraints:
65+
66+
```
67+
error[E0798]: arguments for `"cmse-nonsecure-entry"` function too large to pass via registers
68+
--> $DIR/params-via-stack.rs:15:76
69+
|
70+
LL | pub extern "cmse-nonsecure-entry" fn f1(_: u32, _: u32, _: u32, _: u32, _: u32, _: u32) {}
71+
| ^^^^^^^^^^^ these arguments don't fit in the available registers
72+
|
73+
= note: functions with the `"cmse-nonsecure-entry"` ABI must pass all their arguments via the 4 32-bit available argument registers
74+
```
75+
76+
The error is generated during `hir_ty_lowering`, and therefore even a `cargo check` will emit these errors. Note that LLVM also checks the ABI properties, but it generates poor error messages late in the compilation process.
77+
78+
Because rust is not C, we impose a couple additional restrictions, based on how these ABIs are (meant to be) used.
79+
80+
### No Generics
81+
82+
No generics are allowed. That includes both standard generics, const generics, and any `impl Trait` in argument or return position. By extension, `async` cannot be used in combination with the cmse ABIs.
83+
84+
```
85+
error[E0798]: functions with the `"cmse-nonsecure-entry"` ABI cannot contain generics in their type
86+
--> $DIR/generics.rs:69:1
87+
|
88+
LL | extern "cmse-nonsecure-entry" fn return_impl_trait(_: impl Copy) -> impl Copy {
89+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
90+
```
91+
92+
The `cmse-nonsecure-call` calling convention can only be used on function pointers, which already disallows generics. For `cmse-nonsecure-entry`, it is standard to add a `#[no_mangle]` or similar attribute, which also disallows generics. Explicitly disallowing generics enables the layout calculation that is required for good error messages for signatures that use too many registers.
93+
### No C-variadics (currently)
94+
95+
Currently both ABIs disallow the use of c-variadics. For `cmse-nonsecure-entry` the toolchain actually does not support c-variadic signatures (likely because of how they interact with veneers, though the specification does not say that explicitly).
96+
97+
- clang rejects c-variadic entry functions: https://godbolt.org/z/MaPjzGcE1
98+
- but accepts c-variadic nonsecure calls: https://godbolt.org/z/5rdK58ar4
99+
100+
For `cmse-nonsecure-call` we may stabilize c-variadics at some point in the future.
101+
### Warn on unions and niches
102+
103+
Uninitialized memory, such as space not used by the current enum or union variant, or niches in other values, can contain secret information. A warning is emitted when such values cross the security boundary (move from secure to non-secure).
104+
105+
```
106+
warning: passing a union across the security boundary may leak information
107+
--> $DIR/params-via-stack.rs:43:41
108+
|
109+
LL | f4: extern "cmse-nonsecure-call" fn(MaybeUninit<u64>),
110+
| ^^^^^^^^^^^^^^^^
111+
|
112+
= note: the bytes not used by the current variant may contain stale secure data
113+
```
114+
115+
A `cmse-nonsecure-call` function will emit a warning when any of its arguments contains a union or niche, and a `cmse-nonsecure-entry` function warns when it returns a type containing a union or niche.
116+
117+
Even passing a reference emits a warning, because it contains a niche. In practice this is unlikely to come up: the receiving side should validate the address they get anyway, so it would be better to pass a pointer value across the security boundary.
118+
119+
Ultimately guaranteeing the security properties of the system is up to the programmer, but warning on types with potentially uninitialized memory is a helpful signal that the compiler can easily provide.
120+
121+
Clang warns when union values cross the security boundary, see https://godbolt.org/z/vq9xnrnEs.
122+
123+
## Background
124+
125+
Additional background on what these calling conventions do, and how they are meant to be used. This information is not strictly required to understand the RFC, but has informed the design and may explain certain design choices.
34126

35-
## The `extern "cmse-nonsecure-entry`" CC
127+
### The `extern "cmse-nonsecure-entry`" CC
36128

37129
Functions that use the `cmse-nonsecure-entry` calling convention are called *entry functions*.
38130

@@ -104,34 +196,6 @@ unsafe extern "cmse-nonsecure-entry" {
104196
}
105197
```
106198

107-
### Technical details
108-
109-
The `cmse-nonsecure-entry` ABI is only accepted on `thumbv8m` targets, on all other targets it generates an invalid ABI error. In terms of passing arguments and return values, it is based on AAPCS, but imposes additional restrictions.
110-
111-
We mirror the LLVM implementation by restricting the number of parameters, their types, and the return type to avoid using the stack for the passing of arguments or the return value. In combination with unused registers getting cleared when switching security modes these restrictions guarantee that secret information cannot accidentally leak.
112-
113-
An error is emitted when the program contains a signature that violates the calling convention's constraints:
114-
115-
```
116-
error[E0798]: arguments for `"cmse-nonsecure-entry"` function too large to pass via registers
117-
--> $DIR/params-via-stack.rs:15:76
118-
|
119-
LL | pub extern "cmse-nonsecure-entry" fn f1(_: u32, _: u32, _: u32, _: u32, _: u32, _: u32) {}
120-
| ^^^^^^^^^^^ these arguments don't fit in the available registers
121-
|
122-
= note: functions with the `"cmse-nonsecure-entry"` ABI must pass all their arguments via the 4 32-bit available argument registers
123-
```
124-
125-
The error is generated during `hir_ty_lowering`, and therefore even a `cargo check` will emit these errors. Note that LLVM also checks the ABI properties, but it generates poor error messages late in the compilation process.
126-
127-
Entry functions cannot be c-variadic (see https://godbolt.org/z/MaPjzGcE1). The official specification does not explicitly mention why. The reason seems to be that the veneer cannot forward the c-variadic arguments correctly.
128-
129-
Entry functions use the special `BXNS` instruction to return to their non-secure caller. To ensure that secure information does not leak, LLVM emits instructions to clear caller-saved registers before returning.
130-
131-
It is sound to cast a `extern "cmse-nonsecure-entry" fn` to a `extern "C" fn`: its argument/return passing rules are a strict subset of the platform's C calling convention. When the system is already in secure mode, the `BXNS` instruction behaves like a standard return.
132-
133-
Because entry functions are meant to be exported, we disallow `async` and more broadly any function returning `impl Trait`.
134-
135199
### The `extern "cmse-nonsecure-call`" CC
136200

137201
The `cmse-nonsecure-call` calling convention is used for *non-secure function calls*: function calls that switch from secure to non-secure mode. Because secure and non-secure code are separated into different executables, the only way to perform a non-secure function call is via function pointers. Hence, the `cmse-function-call` calling convention is only allowed on function pointers, not in function definitions or `extern` blocks.
@@ -142,39 +206,6 @@ A *non-secure function pointer*, i.e. a function pointer using the `cmse-nonsecu
142206

143207
The secure executable can get its hands on a non-secure function pointer in two ways: the function address can be an argument to an entry function, or it can be in memory at a statically-known address.
144208

145-
### Technical details
146-
147-
The `cmse-nonsecure-call` ABI is only accepted on `thumbv8m` targets, on all other targets it generates an invalid ABI error. In terms of passing arguments and return values, it is based on AAPCS, but imposes additional restrictions.
148-
149-
Like for `cmse-nonsecure-entry`, we restrict the signature of `cmse-nonsecure-call` so that the stack is not used for argument passing or the return value. Similar errors are generated.
150-
151-
With clang, `__attribute__((cmse_nonsecure_call))` functions can be c-variadic https://godbolt.org/z/5rdK58ar4, but the argument limits still apply. The current rust implementation does not yet allow functions with this calling convention to be c-variadic.
152-
153-
Casting `extern "cmse-nonsecure-call"` to `extern "C"` is not unsound, but calling such a function will trigger a HardFault.
154-
155-
Because `cmse-nonsecure-entry` is only allowed on function pointers, functions with this ABI cannot be `async`, and cannot return any `impl Trait` values.
156-
157-
## Unions & Niches
158-
159-
Uninitialized memory, such as space not used by the current enum or union variant, or niches in other values, can contain secret information. A warning is emitted when such values cross the security boundary (move from secure to non-secure).
160-
161-
```
162-
warning: passing a union across the security boundary may leak information
163-
--> $DIR/params-via-stack.rs:43:41
164-
|
165-
LL | f4: extern "cmse-nonsecure-call" fn(MaybeUninit<u64>),
166-
| ^^^^^^^^^^^^^^^^
167-
|
168-
= note: the bytes not used by the current variant may contain stale secure data
169-
```
170-
171-
A `cmse-nonsecure-call` function will emit a warning when any of its arguments contains a union or niche, and a `cmse-nonsecure-entry` function warns when it returns a type containing a union or niche.
172-
173-
Even passing a reference emits a warning, because it contains a niche. In practice this is unlikely to come up: the receiving side should validate the address they get anyway, so it would be better to pass a pointer value across the security boundary.
174-
175-
Ultimately guaranteeing the security properties of the system is up to the programmer, but warning on types with potentially uninitialized memory is a helpful signal that the compiler can easily provide.
176-
177-
Clang warns when union values cross the security boundary, see https://godbolt.org/z/vq9xnrnEs.
178209
# Drawbacks
179210
[drawbacks]: #drawbacks
180211

0 commit comments

Comments
 (0)