Compiler-instrumented Stack Canaries

With stack protector enabled, the compiler saves a guard value in selected stack frames and checks it again before returning. If the value has changed, the function calls __stack_chk_fail(). In OP-TEE, the reference guard is stored in __stack_chk_guard.

The compiler arguments below are standard stack protector options supported by both GCC and LLVM/Clang.

Compiler argument

Effect

-fstack-protector

Protects functions selected by the compiler’s basic stack protector heuristics.

-fstack-protector-strong

Extends protection to more functions, including ones with local arrays, alloca() calls or address-taken local variables.

-fstack-protector-all

Protects all functions.

-fno-stack-protector

Disables stack protector instrumentation.

Note

This is separate from the thread stack boundary canaries in core/kernel/thread.c controlled by CFG_WITH_STACK_CANARIES.

Pseudo TAs are part of the OP-TEE core image and therefore follow core stack protector settings, not TA stack protector settings.

Stack canaries for OP-TEE core

How to enable stack canaries for OP-TEE core

The OP-TEE core build accepts the following options:

The CFG_* option enables the matching compiler argument. The Build logic column shows where that argument is added to the build. These options are defined in mk/config.mk.

These flags are added by the common core build logic in core/core.mk. They are not specific to RISC-V; Arm uses the same selection mechanism.

CFG name

Compiler argument

Build logic

Default

CFG_CORE_STACK_PROTECTOR

-fstack-protector

core/core.mk

n

CFG_CORE_STACK_PROTECTOR_STRONG

-fstack-protector-strong

core/core.mk

y

CFG_CORE_STACK_PROTECTOR_ALL

-fstack-protector-all

core/core.mk

n

With none of the three options selected, the core build uses -fno-stack-protector.

Core implementation on RISC-V

RISC-V platforms use the default weak implementation of plat_get_random_stack_canaries() from core/kernel/boot.c. It fills one or more canaries with crypto_rng_read() and then clears the low byte of each value. This leaves a null byte in the guard, which helps against some string-based overwrite patterns.

With CFG_NS_VIRTUALIZATION=y, the default weak implementation cannot use the core RNG at that point and falls back to a fixed value, while printing a warning. Platforms using virtualization should override plat_get_random_stack_canaries() if compiler stack protection or thread stack boundary canaries are required.

After boot_init_primary_final(), the RISC-V entry code in core/arch/riscv/kernel/entry.S fetches one native-word canary and writes it to __stack_chk_guard. The guard variable and __stack_chk_fail() live in lib/libutils/isoc/stack_check.c. A failed check prints stack smashing detected and then panics the core.

Stack canaries for user mode TAs

How to enable stack canaries for TAs

User mode TAs and the TA dev kit libraries accept the following options:

The CFG_* option enables the matching compiler argument. The Build logic column shows where that argument is added to the build. These options are defined in mk/config.mk.

These flags are added by the common TA build logic in ta/ta.mk. They are not specific to RISC-V, and the same mechanism is used for other supported TA architectures.

CFG name

Compiler argument

Build logic

Default

CFG_TA_STACK_PROTECTOR

-fstack-protector

ta/ta.mk

n

CFG_TA_STACK_PROTECTOR_STRONG

-fstack-protector-strong

ta/ta.mk

y

CFG_TA_STACK_PROTECTOR_ALL

-fstack-protector-all

ta/ta.mk

n

With none of the three options selected, the TA build uses -fno-stack-protector.

The TA startup code in ta/user_ta_header.c initializes the guard lazily in __ta_entry() before dispatching to __utee_entry().

__ta_entry() is marked __no_stack_protector so it can seed the guard before any compiler-generated canary check applies to that function itself.

When _CFG_TA_STACK_PROTECTOR is enabled and the TA enters for the first time, __ta_entry() calls _utee_cryp_random_number_generate() for a uintptr_t canary, clears its low byte to leave a null byte in the guard, stores the result into __stack_chk_guard, and records that the guard has been initialized.

Subsequent entries reuse the same guard for the lifetime of the TA instance.

If a protected TA function detects corruption, __stack_chk_fail() in lib/libutils/isoc/stack_check.c prints stack smashing detected and calls _utee_panic(TEE_ERROR_OVERFLOW).

TA implementation on RISC-V

RISC-V user mode TAs use the generic TA stack protector initialization path in ta/user_ta_header.c.

User mode TA support is still platform-specific on RISC-V:

Platform file

User mode TA support

core/arch/riscv/plat-virt/conf.mk

Exposes ta_rv64

core/arch/riscv/plat-sifive/conf.mk

Exposes ta_rv64

core/arch/riscv/plat-spike/conf.mk

Forces CFG_WITH_USER_TA=n

References

For compiler-side flag semantics, see GCC and Clang.