12. C29x Security Model

12.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.

12.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.

12.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.

12.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.

12.3.2. Hand-Written Assembly

The compiler automatically generates information for each C/C++ function it emits to object or assembly files to communicate information to the linker. When writing assembly functions directly, this information must be manually communicated through the use of assembly directives:

  • .c29_arg_rmask and .c29_ret_rmask

These two attributes indicate the PRESERVE mask to use when generating trampolines and landing pads (See “Bridging” below) to or from the function named by the attribute’s first argument string. .c29_arg_rmask is the mask used by the caller to ensure that argument registers are not cleared during the “protected” call. .c29_ret_rmask is the mask used by the callee to ensure the register return value from the function is not cleared by the “protected” return. A value of 0 indicates that the function is passed or returns no registers, respectively.

    .global func_i32_f32_returns_pointer
    .c29_arg_rmask      func_i32_f32_returns_pointer,16781312 ; Arguments passed in D0 and M0
    .c29_ret_rmask      func_i32_f32_returns_pointer,16; Returns pointer in A4
; int *func_i32_f32(int, float) { return 0; }
func_i32_f32_returns_pointer:
       ZERO A4
       RET

Warning

The .c29_arg_rmask and .c29_ret_rmask attributes must be present for every hand-written assembly function. The behavior of a secure program is undefined otherwise.

  • .c29_secure_frame

This attribute asserts that the argument string is a function that begins with a security handshake instruction packet and returns control to the caller with a “protected” return instruction. If the function does not contain these special instructions, the behavior of the resulting program is undefined.

    .global secure_function
    .c29_secure_frame secure_function
secure_function:
       ENTRY1.PROT
    || ENTRY2.PROT
       RET.PROT
  • .c29_has_stack_args

This attribute indicates that the stack is used to pass information from a caller to the functions named by the attribute’s argument string. The .c29_stack_args attribute is used to identify and generate error messages in the linker and is otherwise not necessary for correctness.

    .global returns_structure_type
    .c29_has_stack_args returns_structure_type
returns_structure_type:
       MV A5, @global_struct
       MV D0, 0x14 ; assume global_struct is 20 bytes
       CALL @memcpy ; A4=Stack address destination, A5=Copy source, D0=Bytes to copy
       RET

12.4. Security Support in the Linker

12.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.

12.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.

12.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.

12.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.