Security in Sentry
Security requirements
Multiple security considerations have been taken into account at design time. These considerations are the consequence of a security study with respect for the core usage of the Camelot OS and the Sentry kernel, which targets small embedded systems.
The consideration are the following:
As much as possible properties must be a build-time information that can be verified before flashing the target. To achieve that, Sentry kernel consider a lot of information as being build-time produced. Typically:
task list
each task capabilities
each task memory mapping
each task list of allowed devices
each task list of allowed shared memory
each task list of allowed DMA streams
each task integrity signature (HMAC generation)
each task metadata integrity signature (HMAC generation)
device tree configuration (for both kernel and userspace driver)
IPC communication channel allowance between tasks
Exchanges between tasks are controlled, as such, the following restrictions exists:
no broadcast IPC emission
no broadcast signal emission
a device (as declared in the device tree) is strictly dedicated to a given task (including pinmuxes for LEDs for e.g.)
a shared-memory can be mapped at most in two task at a time
shared memory ownership is always kept under the control of the initial SHM declarator
Exchange between tasks and Sentry kernel
No pointer is ever transmitted between userspace tasks and Sentry kernel. Instead tasks are using handles and a dedicated small memory region denoted svc_exchange for non-scalar data transmission and syscall data return (see userspace / kernelspace data transmission concepts chapter).
memory mapping is fully restricted
except SHM, task memory mapping are strictly separated (check at build time)
Sentry kernel is a monitor, not god: it do not have access to task memory map, excepting the task svc_exchange. Only handler entrypoint manipulate the task stack for saving/restoring the task context, just before demapping the task region
All mapped regions respect the W^X principle
security events consideration
Security related events are considered in Sentry, through the usage of the sigabort post-mortem service. Any abnormal event that generate an invalid task termination generate a call to this runtime post-mortem service in the task context with a fresh stask. If the task developer implement a sigabort hook, it will be executed
In release mode, any security level return code (abnormal return code considered as a security exception) generates a panic call which lead to a full reset
TBD: any abnormal event that generate a board reset aim to be stored in a dedicated, encrypted, flash memory dedicated area, in a specific binary formatted structure for post-mortem analysis after dump and decryption
Sentry kernel include various security whatchdogs that are proactively executed regulary (at syscall time or schedule time) to check kernel-related content state (MPU configuration, hardware registers states, etc.) Any fail lead to a security panic().
Todo
Post-mortem service, _exit exit point and watchdogs still need to be implemented.
bootup and modes
- Sentry build natively supports three modes: debug, autotest and release
debug: debug manager is compiled and linked, adding usart and other debug features. Some debug-related feature that may reduce security can be activated. The build system inform the developer that the kernel is built in debug mode and MUST NOT be used in production
autotest: debug manager has debug line activated, but other features are in release mode. The kernel do not parse the task metadata list but instead spawn a single task denoted autotest in order to execute various uapi and kernel-space tests (security, performances) on the hardware target. See autotest chapter for more informations.
release: all debug are deactivated, all enforcement are activated and can’t be deactivated even through the configuration GUI. This include overall bootup integrity checks (task HMACs), MPU hardening and various periodic security watchdogs
About stack smashing protection
Stack smashing protection is a basic protection mechanism that include in each stack frame a guardian that is forged at runtime during function preamble and checked at function postambule.
This guardian is denoted Canary and is manipulated by compiler’s primitives that are embedded in the software runtime, called during each stack frame creation and leave. This primitives are activated with the usual stack-protector, stack-protector-strong, flags, and need to be seeded at runtime for each thread in order to generate per-stack entropy, so that each thread has its own canary sequence.
In order to seed each task thread, Sentry kernel, in association with the userspace _start entrypoint implementation, deliver a per-job seed. The seed is pushed, at job startup, as the second argument of the entrypoint. On ARM architecture, the ARM calling convention is using r0-r3 for the fourth first function arguments, and as such, the seed is passed directly through the r1 register at job bootup.
This requires that the entrypoint respect the _start symbol as defined in job entrypoint section.
Note
The usage of _start symbol in the application runtime allows to properly forge application environment at job boot time, and properly support application termination at job end time without requiring any single line of code from the application developer
Warning
The _start implementation, while being a part of the overall runtime, is not under Sentry responsibility, but instead hosted in the userspace runtime, typically libShield for POSIX or Rust Sentry HAL
About compile-time hardening
Sentry kernel natively supports compiler-delivered hardening options, automatically checked at configure time.
Supported compiler featuresets are the following:
harden-compares: For every logical test that survives GIMPLE optimizations and is not the condition in a conditional branch (for example, conditions tested for conditional moves, or to store in boolean variables), emit extra code to compute and verify the reversed condition, and to call __builtin_trap if the results do not match.
harden-conditional-branches: For every non-vectorized conditional branch that survives gimple optimizations, emit extra code to compute and verify the reversed condition, and to call __builtin_trap if the result is unexpected. Use with -fharden-compares to cover all conditionals.
harden-control-flow-redundancy: Emit extra code to set booleans when entering basic blocks, and to verify and trap, at function exits, when the booleans do not form an execution tree that is compatible with the control flow graph.
hardcfr-check-returning-calls: When -fharden-control-flow-redundancy is active, check the recorded execution path against the control flow graph before any function call immediately followed by a return of its result, if any, so as to not prevent tail-call optimization, whether or not it is ultimately optimized to a tail call.
These flags are mostly activated starting with gcc >= 14.
Note
As these flags may highly impact the generated kernel size, they are not activated by default. The flag activation can easily be made using menuconfig, or set into a given defconfig file.
Todo
The __builtin_trap symbol must be wrapped so that we take control on fault detection
About the usage of anti-temper detection coprocessor
Typically, on stm32u5 SoC, the TAMP coprocessor is used in order to support fast key erasing on tampering detection. The key erasing is also triggered when the __builtin_trap is executed.
Todo
The trap to temper subsystem still need to be implemented, starting with STM32U5 SoC but may also be considered on other SoCs that have an equivalent anti-tempering IP