You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/0000-cmse-calling-conventions.md
+29-24Lines changed: 29 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,42 +15,48 @@ Rust and Trustzone form an excellent pairing for developing embedded projects th
15
15
16
16
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.
17
17
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.
19
19
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.
21
25
22
26
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.
23
27
24
28
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.
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.
29
33
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.
31
35
32
36
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.
33
37
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.
35
39
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.
37
41
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.
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.
41
47
## ABI Details
42
48
43
49
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.
44
50
45
51
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"`.
46
52
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"`.
48
54
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.
50
56
51
57
### Argument passing
52
58
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.
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.
103
109
### No C-variadics (currently)
104
110
105
111
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
111
117
112
118
## No tail calls
113
119
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:
115
121
116
122
> 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
117
123
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.
119
127
### Support for `const fn`
120
128
121
129
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
171
179
172
180
Functions that use the `cmse-nonsecure-entry` calling convention are called *entry functions*.
173
181
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
-
176
182
An entry function has two ELF symbols labeling it:
177
183
178
184
- the standard rust symbol name
179
185
- A special symbol that prefixes the standard name with `__acle_se_`
180
186
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.
182
188
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:
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.
222
228
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.
This works because the secure and non-secure applications share their address space, they just each use different chunks of that address space.
242
249
### The `extern "cmse-nonsecure-call`" CC
243
250
244
251
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
261
268
262
269
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.
263
270
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.
265
272
# Prior art
266
273
[prior-art]: #prior-art
267
274
@@ -277,9 +284,7 @@ The [`cortex_m`](https://docs.rs/cortex-m/latest/cortex_m/cmse/index.html) crate
277
284
# Unresolved questions
278
285
[unresolved-questions]: #unresolved-questions
279
286
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.
0 commit comments