72、嵌入式系统安全开发:故障处理、项目创建与工具链支持

嵌入式系统安全开发:故障处理、项目创建与工具链支持

1. 故障处理机制

当使用TrustZone安全特性时,安全固件需要进行特定配置。硬故障(HardFault)和总线故障(BusFault)异常应指向安全状态,即AIRCR.BFHFNMINS位需保持为0。同时,建议对安全固件中的故障异常进行设置,防止在安全世界触发故障异常后,非安全代码在故障安全上下文中触发进一步操作,比如非安全到安全的函数调用、异常返回等。

若损坏的是安全进程栈(PSP_S),且与之关联的安全上下文(软件线程)可终止,那么恢复正常执行是安全的。此时,安全软件可选择包含回调API功能,以便在发生故障时通知非安全软件。若损坏的是安全主栈,或者故障安全上下文无法终止,则应重启系统。

为进一步降低安全风险,安全软件可通过将安全世界中故障异常的优先级设置得高于非安全异常,防止非安全软件在安全世界发生故障事件后对其发起攻击。具体可通过以下两种方式实现:
- 设置AIRCR.PRIS为1,并确保安全世界的安全故障异常(BusFault、UsageFault、SecureFault和MemManage fault)处于异常优先级范围0到0x7F。
- 不启用安全世界中的BusFault、UsageFault、SecureFault和MemManage故障,使针对安全状态的故障事件升级为安全硬故障。

由于HardFault和BusFault异常指向安全状态,非安全软件开发者在调试时难以确定软件故障的原因,因为这些故障异常的状态信息在非安全世界不可访问。为便于调试部分故障事件,使用Cortex - M33处理器的非安全软件开发者应启用UsageFault和MemManage故障,并将这些故障异常的优先级设置得高于其他中断,从而可在非安全环境中调试这些故障事件。

为帮助非安全软件开发者调试软件,安全软件可利用通信接口向开发者报告故障事件的发生。安全固件中的故障处理程序直接处理消息输出(如使用ITM功能)比调用非安全函数处理错误消息更安全。若没有通信接口,可在项目中声明非安全RAM缓冲区,安全固件将错误消息输出到该缓冲区,方便非安全软件开发者提取消息。

2. 在Keil MDK中创建安全项目
2.1 创建安全项目

开发安全项目时,通常需同时创建非安全项目,以测试安全与非安全世界之间的接口。使用支持多项目工作区的工具链(如Keil MDK)会有所帮助。创建安全项目的一般步骤如下:
1. 创建安全项目:使用安全软件环境的项目设置(如启用安全代码编译的编译器命令行选项)。
2. 创建非安全项目:使用非安全软件环境的项目设置。
3. 创建多项目工作区,并将安全和非安全项目都添加到其中。

创建此示例安全项目时,将使用与之前示例项目相同的FPGA硬件平台(带有Cortex - M33处理器的MPS2 +)。按照特定步骤创建安全项目,需在目标选项卡中选择“Secure Mode”。为简化示例,假设安全世界不使用FPU。

接下来重要的是配置链接器设置。默认情况下,项目从目标对话框获取内存映射设置。对于安全项目,需要自定义NSC区域的布局,因此链接器设置与默认设置不同。链接器分散文件(example_s.sct)包含以下设置:

LR_IROM1 0x10000000 0x00200000 { ; load region (size of region=0x00200000)
    ER_IROM1 0x10000000 0x00200000 { ; load address = execution address
        *.o (RESET, +First)
        *(InRoot$$Sections)
        .ANY (+RO)
        .ANY (+XO)
    }
    EXEC_NSCR 0x101F0000 0x10000 {
        *(Veneer$$CMSE)
        ; check with partition.h
    }
    RW_IRAM1 0x38000000 0x00200000 { ; RW data
        .ANY (+RW +ZI)
    }
}

IDE生成的默认分散文件与自定义版本的区别在于,后者对安全入口点使用的内存区域有不同设置,由分散文件中包含“Veneer$$CMSE”的部分指示。此地址范围需与CMSIS - CORE文件“partition_ .h”中定义的非安全可调用(NSC)区域设置相匹配,否则安全调用可能无法正常工作,或不匹配可能导致安全漏洞。

设置安全项目的最后一步是添加以下命令,指示链接器生成导出库,该命令插入链接器设置对话框的“Misc controls”中:

--import_cmse_lib_out=secure_api.lib

示例程序代码包含以下操作:
- 初始切换到非安全世界。
- 非安全软件调用安全API。
- 从非安全世界向安全世界传递回调函数指针。
- 安全软件调用非安全世界中的回调函数。

以下是示例安全代码:

#include <arm_cmse.h>
#include "IOTKit_CM33_FP.h"
#include "stdio.h"

typedef int __attribute__((cmse_nonsecure_call)) tdef_nsfunc_o_int_i_void(void);
typedef int __attribute__((cmse_nonsecure_call)) tdef_nsfunc_o_int_i_int(int x);

int __attribute__((cmse_nonsecure_entry)) entry1(int x);
int __attribute__((cmse_nonsecure_entry)) pass_nsfunc_ptr_o_int_i_int
(tdef_nsfunc_o_int_i_int *callback);

int nonsecure_init(void);
void default_callback(void);
int call_callback(int data_in);

// Declare function pointer *fp
// *fp can point to either a secure function or a non-secure function
// Initializes to a default callback
tdef_nsfunc_o_int_i_int *fp = (tdef_nsfunc_o_int_i_int *) default_callback;

int main(void)
{
    printf("Secure Hello world\n");
    nonsecure_init();
    while(1);
}

int nonsecure_init(void) {
    // Example modified from
    // https://www.community.arm.com/iot/embedded/b/blog/posts/a-few-intricacies-of-
    // writing-armv8-m-secure-code
    // If needed, setup the Non-secure VTOR.
    // In the Cortex-M33 based FPGA platform used for creating this example
    // (i.e. a system called IoT Kit, which runs on the MPS2 FPGA board), the
    // Non-secure code image starting address is 0x00200000
    SCB_NS->VTOR=0x00200000UL; // Optional for most hardware platforms.
    // But it is needed in the FPGA platform used here.
    // The following line creates a pointer to the Non-secure vector table
    uint32_t *vt_ns = (uint32_t *) SCB_NS->VTOR;
    // Setup Non-secure Main Stack Pointer (MSP_NS)
    __TZ_set_MSP_NS(vt_ns[0]);
    // Setup the function pointer to NS reset vector
    tdef_nsfunc_o_int_i_void *ns_reset = (tdef_nsfunc_o_int_i_void*)(vt_ns[1]);
    // Branch into the Non-secure reset handler
    ns_reset(); // Branch to the Non-secure world

    // If Reset Handler executes a return, the program execution will reach here
    printf("ERROR: should not be here\n");
    while(1);
}

// This is a Secure API
int __attribute__((cmse_nonsecure_entry)) entry1(int x)
{
    int result;
    result = call_callback(x);
    return (result);
}

// This is a Secure API with a function pointer as the input parameter
int __attribute__((cmse_nonsecure_entry)) pass_nsfunc_ptr_o_int_i_int
(tdef_nsfunc_o_int_i_int *callback) {
    // Result for function pointer
    cmse_address_info_t tt_payload;
    tt_payload = cmse_TTA_fptr(callback);
    if (tt_payload.flags.nonsecure_read_ok) {
        fp = cmse_nsfptr_create(callback); // non-secure function pointer
        return (0);
    } else {
        printf ("[pass_nsfunc_ptr_o_int_i_int] Error: input pointer is not NS\n");
        return (1); // Function pointer is not accessible from the Non-secure side
    }
}

// Call back a Non-secure function
int call_callback(int data_in) {
    if (cmse_is_nsfptr(fp)){
        return fp(data_in);
        // Non-secure function call
    } else {
        ((void (*)(void)) fp)(); // Call default callback as normal function call
        return (0);
    }
}

// Default callback function
void default_callback(void) {
    __BKPT(0);
    while(1);
}

除创建示例安全程序代码外,还需根据项目要求更新地址分区设置(如SAU、MPC和PPC)。此示例使用图中详细说明的SAU设置,MPC和PPC的设置位于“system_ .c”文件中。

设置完成后,可编译安全项目,生成非安全项目所需的导出库“secure_api.lib”。此时可关闭安全项目,为非安全世界打开新的项目。

3. 创建非安全项目

创建非安全项目的步骤与安全项目基本相同,但有以下几点不同:
1. 项目设置为非安全模式。
2. 内存映射设置为非安全内存。
3. 可使用项目目标对话框中的内存映射设置创建链接器设置。
4. 需要添加从安全项目生成的导出库到非安全项目。

非安全项目名为example_ns,需选择非安全内存,并将“Startup”设置为非安全内存程序。

非安全项目的程序代码如下:

#include "IOTKit_CM33_FP.h"
#include "stdio.h"

extern int entry1(int x);
extern int pass_nsfunc_ptr_o_int_i_int(void *callback);

int my_func(int data_in); // Call back function

int main(void)
{
    int status;
    printf("Non-secure Hello world\n");
    // Pass a Non-secure function pointer to the Secure world
    status = pass_nsfunc_ptr_o_int_i_int(& my_func);
    if (status==0) {
        // Call a Secure function
        printf ("Result = %d\n", entry1(10));
    } else {
        printf ("ERROR: pass_nsfunc_ptr_o_int_i_int() = %d\n", status);
    }

    // Normally, Non-secure software only passes a Non-secure function
    // pointer to the Secure world. In this example, because I want to
    // illustrate the checking of the function pointer by a Secure API, an
    // attempt is made to pass a function pointer with a Secure address.
    status = pass_nsfunc_ptr_o_int_i_int((void* )0x100000F1UL);
    if (status==0) {
        // Call secure function
        printf ("Result = %d\n", entry1(10));
    } else {
        printf ("Expected: pass_nsfunc_ptr_o_int_i_int() = %d\n", status);
    }
    printf("Test done\n");
    while(1);
}

int my_func(int data_in)
{
    printf("[my_func]\n");
    return (data_in * data_in);
}

除创建非安全程序代码外,还需将导出库“secure_api.lib”添加到项目中。完成此步骤后,非安全项目即可进行编译。应关闭非安全项目,创建多项目工作区。在示例项目中,创建了名为“example”的多项目工作区,并将“example_s”(安全)和“example_ns”(非安全)项目添加到其中。

4. 创建多项目工作区

创建多项目工作区可使用下拉菜单“Project ! New multi - project workspace”。在“Create New Multi - Project Workspace”对话框中,点击右上角的“New (Insert)”图标,然后点击行右侧的“…”按钮,依次添加“example_s”和“example_ns”项目。

添加项目后,两个项目将列在项目窗口的工作区中。使用多项目工作区时,需选择一个项目作为“Active Project”,以便确定进行编译、调试等操作的项目。可通过在项目窗口中右键单击项目名称,然后选择“Set as Active Project”来选择活动项目。

在硬件上运行示例程序前的最后一步是设置调试选项。除之前提到的一些调试设置外,可能还需设置调试选项,使调试会话开始时同时将两个镜像加载到设备中。使用带有闪存的设备时,此设置为可选,因为程序镜像编译后可单独下载,将两个镜像都编程到闪存后即可测试软件。但如果不使用调试设置确保将安全和非安全程序镜像都编程到闪存,软件测试和调试过程容易出错,因为容易忘记编程其中一个程序镜像。

通过使用调试脚本,可确保在调试会话开始时将两个编译好的镜像加载到设备中。如果活动项目是非安全项目,调试脚本还可用于配置调试会话,使其从安全启动的开始处启动。

非安全调试脚本“ns_debug.ini”示例如下:

FUNC void Setup (void) {
    _WDWORD(0x50021104, 0x00000010);
    // Sets bit 4 of the RESET_MASK register
    // Setup SP and PC to use values for Secure software
    SP = _RDWORD(0x10000000);
    // Sets up the Stack Pointer
    PC = _RDWORD(0x10000004);
    // Sets up the Program Counter
}

LOAD "Objects\\example_s.axf" incremental
LOAD "Objects\\example_ns.axf" incremental
Setup();
RESET
/* Resets the target processor */

安全调试脚本“s_debug.ini”如下:

FUNC void Setup (void) {
    _WDWORD(0x50021104, 0x00000010);
    // Sets bit 4 of the RESET_MASK register
}

LOAD "Objects\\example_s.axf" incremental
LOAD "Objects\\example_ns.axf" incremental
Setup();
RESET
/* Resets the target processor */

安全调试脚本比非安全调试脚本简单,因为默认情况下,调试会话已为调试安全软件环境设置好相关设置。

5. 其他工具链对CMSE的支持
5.1 GNU C编译器(GCC)

使用Arm GCC编译器与Armv8 - M处理器时,需使用“-mcmse”命令行选项编译安全状态软件。

使用GCC指定安全软件中非安全可调用(NSC)区域的位置,可采用以下两种方式之一:
- 命令行选项:“–section - start=.gnu.sgstubs=

”。
- 链接器脚本:需创建具有指定运行时地址的“.gnu.sgstubs”输出部分描述。

还需添加两个处理导出库的命令行选项:
- “–out - implib= ”
- “–cmse - implib”

这些选项用于生成导入库。

5.2 IAR

IAR Embedded Workbench® for Arm(EWARM)自7.70版本发布以来支持Armv8 - M架构。使用IAR编译器与Armv8 - M处理器时,需使用“–cmse”命令行选项启用安全状态软件的编译。指定编译针对Armv8 - M处理器,可采用以下两种方式之一:
- 使用处理器选项:如“–cpu = Cortex - M23”或“–cpu = Cortex - M33”。
- 使用架构选项:如“–cpu = 8 - M.baseline”或“–cpu = 8 - M.mainline”。

对于链接器,应使用命令行选项“–import_cmse_lib_out FILE/DIRECTORY”指定导出库,使用“–import_cmse_lib_in FILE”指定导入库。

综上所述,在嵌入式系统开发中,无论是故障处理、项目创建还是工具链的使用,都需要充分考虑安全因素,遵循相应的配置和操作步骤,以确保系统的安全性和稳定性。通过合理运用上述方法和工具,开发者能够更好地开发出安全可靠的嵌入式软件。

嵌入式系统安全开发:故障处理、项目创建与工具链支持(续)

6. 关键技术点分析
6.1 故障处理机制的重要性

故障处理机制是嵌入式系统安全开发的核心环节之一。在使用TrustZone安全特性时,将硬故障和总线故障异常指向安全状态,能够有效防止非安全代码对安全上下文的非法操作。当安全世界发生故障时,根据故障栈的类型和安全上下文的状态采取不同的处理措施,如恢复正常执行或重启系统,有助于保证系统的稳定性和安全性。

例如,在安全进程栈损坏且相关安全上下文可终止的情况下,恢复正常执行可以避免系统不必要的重启,提高系统的可用性。而当安全主栈损坏或故障安全上下文无法终止时,重启系统则是保障系统安全的必要手段。

6.2 安全项目与非安全项目的协同开发

在Keil MDK中同时创建安全项目和非安全项目,并将它们添加到多项目工作区,是测试安全与非安全世界之间接口的有效方法。安全项目和非安全项目的创建步骤虽然有相似之处,但也存在关键差异,如安全项目需要自定义NSC区域的布局,而非安全项目需要添加从安全项目生成的导出库。

通过示例代码可以看到,安全项目和非安全项目之间通过函数调用和回调函数指针的传递实现了交互。例如,非安全软件可以调用安全API,安全软件也可以调用非安全世界中的回调函数,这种交互方式增强了系统的灵活性和功能扩展性。

6.3 工具链对CMSE的支持

不同的工具链对CMSE(Armv8-M的基于上下文的内存保护扩展)的支持方式有所不同。GCC和IAR编译器在编译安全状态软件时都需要特定的命令行选项,并且在指定NSC区域位置和处理导出库方面也有各自的方法。

例如,GCC使用“-mcmse”命令行选项编译安全状态软件,同时可以通过命令行选项或链接器脚本指定NSC区域的位置。IAR编译器则使用“–cmse”命令行选项启用安全状态软件的编译,并通过特定的命令行选项指定导出库和导入库。

7. 总结与建议
7.1 总结

本文详细介绍了嵌入式系统安全开发中的故障处理机制、在Keil MDK中创建安全项目和非安全项目的步骤、创建多项目工作区的方法以及不同工具链对CMSE的支持。通过合理配置故障处理机制、正确创建和配置项目以及选择合适的工具链,开发者可以开发出安全可靠的嵌入式软件。

7.2 建议
  • 故障处理方面 :开发者应深入理解不同故障类型的处理方式,根据系统的实际情况进行合理配置。在安全软件中,应充分利用回调API功能,及时通知非安全软件故障的发生,以便进行相应的处理。
  • 项目创建方面 :在创建安全项目和非安全项目时,要严格按照步骤进行操作,特别是要注意NSC区域的布局和导出库的添加。同时,要确保安全项目和非安全项目之间的接口正确配置,避免出现安全漏洞。
  • 工具链使用方面 :根据项目的需求和特点选择合适的工具链,并熟悉其对CMSE的支持方式。在使用工具链时,要注意命令行选项的正确使用,确保编译和链接过程的顺利进行。
8. 流程图展示
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(创建安全项目):::process
    B --> C(创建非安全项目):::process
    C --> D(创建多项目工作区):::process
    D --> E{选择活动项目}:::decision
    E -->|是| F(设置调试选项):::process
    E -->|否| E
    F --> G(编译项目):::process
    G --> H(运行调试脚本):::process
    H --> I([结束]):::startend

此流程图展示了从项目创建到运行调试脚本的整个过程,帮助开发者更清晰地了解各个步骤之间的关系。

9. 表格对比
工具链 编译安全状态软件选项 指定NSC区域位置方式 处理导出库选项
GCC -mcmse 命令行选项:–section-start=.gnu.sgstubs=

链接器脚本:创建具有指定运行时地址的“.gnu.sgstubs”输出部分描述
–out-implib=
–cmse-implib
IAR –cmse 无特定说明 –import_cmse_lib_out FILE/DIRECTORY
–import_cmse_lib_in FILE

通过表格对比,可以更直观地看到GCC和IAR编译器在支持CMSE方面的差异,方便开发者根据实际情况进行选择。

通过以上内容,开发者可以全面了解嵌入式系统安全开发的相关知识和技术,为开发安全可靠的嵌入式软件提供有力的支持。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值