Skip to content

Commit 75df78e

Browse files
committed
edits
1 parent e54777b commit 75df78e

File tree

1 file changed

+29
-24
lines changed

1 file changed

+29
-24
lines changed

text/0000-cmse-calling-conventions.md

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,48 @@ Rust and Trustzone form an excellent pairing for developing embedded projects th
1515

1616
Trustzone creates a security boundary between a secure and non-secure application. The secure application can work with secure information (e.g. encryption keys). By limiting the interactions between the secure and non-secure applications, large classes of security bugs are statically prevented.
1717

18-
In embedded systems it is common to have an extra physical chip, a secure enclave, to handle secure information. With Trustzone, this additional chip is not needed: a secure enclave is created on the main chip instead.
18+
In embedded systems it is common to have an extra physical chip, a secure enclave, to handle secure information. With Trustzone, this additional chip is not needed: a secure enclave is simulated on the main chip instead.
1919

20-
The cmse calling conventions define the interactions between secure and non-secure code. By automatically checking the constraints placed on secure and non-secure functions, it becomes ergonomic to write secure code. In the absence of compiler support for these calling conventions, global assembly is needed for all calls across the security boundary, which is obviously inconvenient and error-prone.
20+
The secure and non-secure applications communicate over an FFI boundary: the two applications run on the same chip and use the same address space, but are not linked together. The cmse calling conventions are used to cross this FFI boundary, and apply restrictions on how it can be crossed.
21+
22+
Much like how `unsafe` in rust limits where the programmer must be careful not to introduce UB, these special calling conventions clearly mark where secure data might be leaked. They also handle the clearing of registers before the secure boundary is crossed, so that a malicious non-secure application cannot read lingering secure data.
23+
24+
Without compiler support it is much harder to know where to focus review effort, and every call that crosses the secure boundary requires inline assembly, which is inconvenient and error-prone.
2125

2226
A specific use case is encapsulating C APIs. Providing a C interface is still the standard way for a hardware vendor to provide access to system components. Libraries for networking (LTE, Bluetooth) are notorious for their bugs. Running such code in non-secure mode significantly reduces the risk of bugs leaking secure information.
2327

2428
Trustzone is growing in availability and use. More and more of the new medium and large ARM microcontrollers have support. Large industry players have requested Rust support for Trustzone.
2529
# Guide-level explanation
2630
[guide-level-explanation]: #guide-level-explanation
2731

28-
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.
32+
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.
2933

30-
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 or any memory that is marked as secure: the system will raise a SecureFault when a program dereferences a pointer to memory that it does not have access to. In this way a whole class of security issues is simply impossible in the non-secure app.
34+
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 or any memory that is marked as secure: the system will raise a SecureFault when a program dereferences a pointer to memory that it does not have access to. In this way a whole class of security issues is prevented in the non-secure app.
3135

3236
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.
3337

34-
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 (what rust calls a shim, arm calls a *veneer*) 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.
38+
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 (the arm terminology is *veneer*) that switches the security mode, and an import library (an object file with only declarations, pointing to the addresses of the shims, not actual instructions) that can be linked into the non-secure executable.
3539

36-
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.
40+
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.
3741

38-
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.
42+
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, and reports an error if not.
3943
# Reference-level explanation
4044
[reference-level-explanation]: #reference-level-explanation
45+
46+
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.
4147
## ABI Details
4248

4349
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.
4450

4551
The foundation of the cmse ABIs is the platform's standard AAPCS calling convention. On `thumbv8m` targets `extern "aapcs"` is the default C ABI and equivalent to `extern "C"`.
4652

47-
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 "aapcs"`, but calling the function will cause a SecureFault. Casting an `extern "aapcs"` function pointer to a `cmse-nonsecure-call` is valid, but will cause a SecureFault if the function's definition is not in non-secure memory.
53+
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 invalid to cast to or from `extern "aapcs"`.
4854

49-
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 "aapcs"`. Calling the function is valid and will behave as expected in both the secure and non-secure applications. Casting an `extern "aapcs"` function pointer to `cmse-nonsecure-entry` is valid, but will not change the security mode, so calling such a function that was defined in the secure application will SecureFault when called in the non-secure application.
55+
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 "aapcs"`. Calling the function is valid and will behave as expected in both the secure and non-secure applications. Casting from `extern "aapcs"` to `extern "C"` is invalid.
5056

5157
### Argument passing
5258

53-
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 standard 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.
59+
The main technical limitation of the cmse ABIs versus plain AAPCS is that the cmse ABIs cannot use the stack for passing function arguments or return values. That leaves only the 4 standard 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.
5460

5561
```rust
5662
// Valid
@@ -99,7 +105,7 @@ LL | extern "cmse-nonsecure-entry" fn return_impl_trait(_: impl Copy) -> impl Co
99105
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
100106
```
101107

102-
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.
108+
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 pre-monomorphization layout calculation that is required for good error messages for signatures that use too many registers.
103109
### No C-variadics (currently)
104110

105111
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 shim that switches to secure mode), though the specification does not say that explicitly).
@@ -111,11 +117,13 @@ For `cmse-nonsecure-call`, we may support and stabilize c-variadics at some poin
111117

112118
## No tail calls
113119

114-
Neither cmse ABI can support (guaranteed) tail calls, per the LLVM source:
120+
Neither cmse ABI can tail call another function, per the LLVM source:
115121

116122
> For both the non-secure calls and the returns from a CMSE entry function, the function needs to do some extra work after the call, or before the return, respectively, thus it cannot end with a tail call
117123
118-
For `cmse-nonsecure-entry` we emit an error when `become` is used to call a function with this ABI. For `cmse-nonsecure-call` it is not (currently) possible to construct an example that triggers the error, because rust requires the caller and callee of a guaranteed tail call to have the same ABI, and `cmse-nonsecure-call` cannot be used in function definitions.
124+
The unstable implementation of guaranteed tail calls in rust requires the caller and callee to have the same ABI. That means that calls to `cmse-nonsecure-call` are never eligible for a tail call (there are no definitions with this ABI). For tail calls to a `cmse-nonsecure-entry` function we emit an explicit error.
125+
126+
Functions with the `extern "cmse-nonsecure-entry"` ABI may themselves be tail-called, though this is only possible when the function is first cast to `extern "C"` to satisfy the restriction that caller and callee have the same ABI.
119127
### Support for `const fn`
120128

121129
No special support for calling cmse functions is needed.
@@ -171,16 +179,14 @@ Additional background on what these calling conventions do, and how they are mea
171179

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

174-
An entry function should be defined in the secure executable, but can be called in both the secure and non-secure executable. In secure mode entry functions can be called directly, but to be called from the non-secure application the entry function must be declared in an `extern` block.
175-
176182
An entry function has two ELF symbols labeling it:
177183

178184
- the standard rust symbol name
179185
- A special symbol that prefixes the standard name with `__acle_se_`
180186

181-
The presence of the prefixed name is used by the linker to generate a *secure gateway veneer*: a shim that uses the *secure gate* (`sg`) instruction to switch security modes and then branches to the real definition. The non-secure executable must call this shim, not the real definition.
187+
The presence of the prefixed name is used by the linker to generate a *secure gateway veneer*: a shim that uses the *secure gate* (`sg`) instruction to switch security modes and then branches to the real definition. The non-secure executable must call this shim, not the real definition. Calling the read definition from the non-secure executable would cause a SecureFault.
182188

183-
It is customary for entry functions to use `no_mangle`, `export_name` or similar so that the symbol is not mangled. The use of the `cmse-nonsecure-entry` calling convention will make LLVM emit the additional prefixed symbol. For instance this function:
189+
It is customary for entry functions to use `no_mangle`, `export_name` or similar so that the symbol is not mangled. The use of the `cmse-nonsecure-entry` calling convention makes LLVM emit the additional prefixed symbol. For instance this function:
184190

185191
```rust
186192
#[unsafe(no_mangle)]
@@ -189,7 +195,7 @@ pub extern "cmse-nonsecure-entry" fn encrypt_the_data(/* ... */) {
189195
}
190196
```
191197

192-
The generated assembly will start with:
198+
Will generate a symbol with the two labels like so:
193199

194200
```asm
195201
.globl __acle_se_encrypt_the_data
@@ -218,9 +224,9 @@ Disassembly of section .gnu.sgstubs:
218224
10008844: f7f8 bb79 b.w 0x10000f3a <__acle_se_encrypt_the_data> @ imm = #-0x790e
219225
```
220226

221-
The function itself will clear registers as needed (to make sure secrets don't linger there) and switch back to non-secure mode upon returning.
227+
Before returning, entry functions clear all unused registers (to make sure secrets don't linger there). The return instruction switches back to the caller's security state based on the return address.
222228

223-
Additionally a `veneers.o` is produced, which can be linked into the non-secure application. This `veneers.o` just contains the unprefixed symbols but maps them to their veneer addresses. Like an import library, `veneers.o` does not contain any instructions, in fact it does not even have a `.text` section.
229+
Additionally the linker produces a `veneers.o` file, which can be linked into the non-secure application. This `veneers.o` just contains the unprefixed symbols but maps them to their veneer addresses. Like an import library, `veneers.o` does not contain any instructions, in fact it does not even have a `.text` section.
224230

225231
```
226232
> arm-none-eabi-objdump -td target/veneers.o
@@ -239,6 +245,7 @@ unsafe extern "cmse-nonsecure-entry" {
239245
}
240246
```
241247

248+
This works because the secure and non-secure applications share their address space, they just each use different chunks of that address space.
242249
### The `extern "cmse-nonsecure-call`" CC
243250

244251
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.
@@ -261,7 +268,7 @@ The straightforward alternative is to have users emulate these calling conventio
261268

262269
For users, these calling conventions should not come up unless someone seeks them out. Interactions with other language features are similarly only relevant to this niche group of users.
263270

264-
For a true ergonomic experience more work is needed, but we believe this can all be done in the package ecosystem. This includes better integration of the veneer generation and automatic input validation e.g. via a trait that is only implemented for types that are safe to pass across the security boundary.
271+
For a true ergonomic experience more work is needed, but we believe this can all be done in the package ecosystem.
265272
# Prior art
266273
[prior-art]: #prior-art
267274

@@ -277,9 +284,7 @@ The [`cortex_m`](https://docs.rs/cortex-m/latest/cortex_m/cmse/index.html) crate
277284
# Unresolved questions
278285
[unresolved-questions]: #unresolved-questions
279286

280-
- What parts of the design do you expect to resolve through the RFC process before this gets merged?
281-
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
282-
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
287+
- is the lint relying on the (unstable) internals of safe transmute a problem. I believe this is fine because it's just a lint.
283288

284289
# Future possibilities
285290
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)