11. C29x Security Model

11.1. The Safety and Security Unit (SSU)

The TI C29x may take advantage of the special Safety and Security Unit (SSU), which protects code and data within an application. The Implementing Run-Time Safety and Security With the C29x Safety and Security Unit (SPRADK2 <https://www.ti.com/lit/pdf/SPRADK2>__**) application note describes this unit in depth. This page provides an introduction to the SSU.

11.2. Key Terms and Constructs

The SSU is configured with a hierarchy of control:

  • An Access Protection Range (APR) is a region of memory with associated read/write permissions.

  • The LINK is one or more regions of executable code that control data accesses (reads and writes) to both memory and peripherals.

  • The STACK isolates execution contexts and the stack pointer address (A15).

  • The ZONE is configured to control debugging and breakpoint support, as well as firmware update permissions.

A ZONE contains one or more STACKs. A STACK contains one or more LINKs. A LINK is a member of one and only one STACK. A STACK is a member of one and only one ZONE.

APRs are granted or denied read/write access to LINKs via hardware configurations.

In this document, the all-capital APR, LINK, STACK, and ZONE terms refer to the SSU concepts instead of other definitions.

11.3. Inter-STACK Calls

A call from one LINK in a STACK to a different link in another STACK is called an inter-STACK call. Such calls are heavily restricted:

  • A special “protected” call instruction must be used to transfer control from caller to callee.

  • The first instruction fetched from the callee must be a special handshake instruction packet.

  • A special “protected” return instruction must be used to transfer control from callee to caller.

  • The first instruction fetched from the caller upon return must be a special handshake instruction packet.

  • All registers are cleared by both call and return except registers passed to or returned from the callee.

  • The stack pointer (A15) is not shared by the caller and callee.

    • Functions with variadic arguments or those that pass or return structures and/or non-fundamental types use the stack for storage according to the rules of the C29x ABI. Such functions can’t be the callee of an inter-STACK call because the stack pointer changes.

Any call whose caller and callee are in the same STACK may be referred to as an intra-STACK call. Such calls are less restricted. None of the restrictions above apply.

11.3.1. Compiler Support for Inter-STACK Calls

C/C++ functions can be declared/defined using the c29_protected_call function attribute. This attribute causes the c29clang compiler to act as if calls to that function or function type are inter-STACK calls. At the assembly level, the compiler emits “protected” calls and returns with the proper registers preserved in addition to the handshake instructions at both the callee’s entry and at the return address. See Function Attributes for more about function attributes.

For example:

void my_function() __attribute__((c29_protected_call)) {
    return; // The definition of my_function, along with the attribute,
            // results in the correct handshake and protected return.
}
void call_my_function() {
    my_function(); // my_function is declared protected, so this results in
                   // the correct protected call and return handshake.
}

void normal_function();
void (*protected_fn_ptr)() __attribute__((c29_protected_call)) = &normal_function; // Suppressible warning in C
void call_normal_function {
    normal_function(); // Normal call instruction with no handshake

    // Note: normal_function will not have the handshake and protected
    // return unless its definition also has the c29_protected_call
    // attribute.
    protected_fn_ptr(); // Results in protected call and return handshake
}

The c29clang compiler identifies and produces an error for any protected function that cannot be called due to stack requirements from the ABI. Example causes for such errors include passing excessive numbers of integer, pointer, or floating-point arguments in registers, returning a structure type by value, and use of variadic arguments.

11.4. Security Support in the Linker

11.4.1. The SECURE_GROUP Memory Range Attribute

The linker accepts the SECURE_GROUP attribute in the MEMORY region of the linker command file. This attribute declares the type of secure behavior allowed in code placed in that memory range. The syntax is as follows:

SECURE_GROUP ( name [, (public|private)][, reads=(a, b…)][, writes=(a, b,…))

The following example assigns the .text_caller section to the CALLER_GROUP call group:

MEMORY {
    RO_CODE1: origin=0x20, length=0x1000, SECURE_GROUP(CALLER_GROUP)
}
SECTIONS {
    .text_caller : { *(.text.caller) } > RO_CODE1
}

A call group is a functional name for a group of code within a single secure module. The linker assumes that code placed in the same call group can freely call to and return from one another without protection, including sharing the same stack pointer.

The following example extends the above linker command file snippet by adding .text_callee in the CALLEE_GROUP call group:

MEMORY {
    RO_CODE1: origin=0x20, length=0x1000, SECURE_GROUP(CALLER_GROUP)
    RO_CODE2: origin=0x1020, length=0x1000, SECURE_GROUP(CALLEE_GROUP)
}
SECTIONS {
    .text_caller : { *(.text.caller) } > RO_CODE1
    .text_callee : { *(.text.callee) } > RO_CODE2
}

If a caller and callee might be placed in such a way that they are in different call groups, the linker assumes that it is an inter-STACK call and requires protection.

  • If the callee was annotated with the c29_protected_call attribute in the source code, the linker identifies that protection has already been added and continues.

  • If the callee was not annotated with the c29_protected_call attribute in the source code, the linker will issue an error, refusing to allow insecure transfer of control.

11.4.2. Linker-generated Secure Calls (Bridging)

If it’s not feasible to annotate the callee with the c29_protected_call attribute, the linker provides a way to insert the proper CALL.PROT and RET.PROT sequences at the cost of code size, stack usage, and execution cycles. The following example introduces the PUBLIC SECURE_GROUP attribute:

MEMORY {
    RO_CODE1: origin=0x20, length=0x1000, SECURE_GROUP(CALLER_GROUP)
    RO_CODE2: origin=0x1020, length=0x1000, SECURE_GROUP(CALLEE_GROUP, PUBLIC)
}
SECTIONS {
    .text_caller : { *(.text.caller) } > RO_CODE1
    .text_callee : { *(.text.callee) } > RO_CODE2
}

The default, and opposite of the PUBLIC attribute is the PRIVATE attribute, which can be specified for readability, but is not required.

A PUBLIC SECURE_GROUP instructs the linker to transform an insecure transfer of control into a secure form. This is done by inserting veneers called trampolines and landing pads.

  • The linker inserts a trampoline if the call site is not secure. A trampoline contains code to:

    • Save every register on the stack.

    • Securely transfer control to and receive the protected return from the callee.

    • Restore the saved registers.

    • Transfer control back to the caller.

  • A landing pad is inserted if the callee is not secure. A landing pad contains code to:

    • Receive the protected call from the caller.

    • Insecurely transfer control to the callee.

    • Securely transfer control back to the caller.

The linker generates both veneers if the call site or callee are insecure. However, if either the call site’s function pointer type or the callee’s definition are marked with the c29_protected_call attribute, the associated veneer is not necessary, saving a portion of the overhead in cycles and memory usage. This is especially true for the call site, as the register saves take a considerable amount of time and space.

11.4.3. Memory Access Protections

Outside of secure calls and returns, the C29 Safety and Security Unit (SSU) provides memory access protections. While two functions might be part of the same STACK, they may not necessarily have read/write access to the same APRs. The READS and WRITES attributes of SECURE_GROUP describe an abstract form of these access permissions:

MEMORY {
    RO_CODE1: origin=0x20, length=0x1000, SECURE_GROUP(SAME_GROUP, READS=(DATA1))
    RO_CODE2: origin=0x1020, length=0x1000, SECURE_GROUP(SAME_GROUP, READS=(DATA2))
    DATA1: origin=0x10000000, length=0x1000
    DATA2: origin=0x10002000, length=0x1000
}
SECTIONS {
    .text_caller : { *(.text.caller) } > RO_CODE1
    .text_callee : { *(.text.callee) } > RO_CODE2
}

In this example, the caller and callee are within the same group, but the linker observes that the caller may access DATA1 while the callee may access DATA2. While a call between caller and callee need not be protected, there is an expectation that the code in callee should not be executed in caller, and vice versa. This has consequences when considering interprocedural optimization.

Note that the arguments to the READS and WRITES list are informative strings and may be any valid identifier. For simplicity, it’s often useful to define them in terms of MEMORY ranges that will be accessible.

11.4.5. Optimization and Security Caveats

Within a single file (after preprocessing, so including headers), the only guaranteed security control is the c29_protected_call attribute. For example, a function marked with this attribute will not be inlined at any call site.

Because there is no guarantee of security, the data and code seen within a single compilation unit (a file with all includes expanded and macros replaced) is considered visible and accessible to all code in the same unit. Projects should be structured such that sensitive or proprietary code and data is not visible to untrusted modules. For example, avoid defining sensitive or proprietary functions in header files that may be included from untrusted modules.