ESP 特权隔离机制—案例研究
中国,上海
2022年8月2日
在前一篇文章中,我们介绍了 ESP 特权隔离机制,并借此在 ESP32-C3 SoC 上实现了“用户-内核”应用程序的相互隔离与独立。目前,您可以通过多种方法在您的项目中部署 ESP 特权隔离机制。本案例研究以 ESP RainMaker 为例,展示了如何将 ESP 特权隔离机制部署至一个真实的物联网应用程序。
ESP RainMaker 是一个完整的 AIoT 平台,可助力客户快速开发 AIoT 产品,详见这里。
在 ESP RainMaker 中部署 ESP 特权隔离机制
1. 创建项目目录
如需应用 ESP 特权隔离机制,则项目的目录结构将与常规 ESP-IDF 项目略有差异。ESP 特权隔离项目在创建目录结构时,需要将程序放在两个子目录下:受保护的应用程序和用户应用程序,并保证这两个子文件夹中的应用程序均可被编译系统编译。以下为一个例子:
rmaker_switch | — CMakeLists.txt | — partitions.csv | — protected_app/ | | — main/ | | — CMakeLists.txt | | — protected_main.c | — user_app/ | — main/ | | — CMakeLists.txt | | — user_code.c | — user_config.h | — CMakeLists.txt
有关“目录结构”的详细说明,请见“ESP 特权隔离机制入门指南”文档。
2. 安装 ESP RainMaker 代理
根据系统调用的具体实现情况,我们可以选择将 ESP RainMaker 代理安装在两个位置:
- 选项 1:用户应用程序中
- 选项 2:受保护的应用程序中
在本文中,我们将以选项 2 为例,具体描述如何将 ESP RainMaker 代理安装至受保护的应用程序文件夹中。
3. 划分各组件至受保护应用程序和用户应用程序
上图展示了各组件在受保护应用程序和用户应用程序之间的分布情况。具体来说,所有库(如 ESP RainMaker、TLS 栈等)因承担了大部分繁重工作,都被放置在受保护的应用程序中;用户应用程序则主要包括与业务相关的轻量级应用。
构建系统将在 build 文件夹下生成 app_libs_and_objs.json 文件,该文件描述了所有库、其占用的内存及对应应用程序中包括的 object 文件。
4. 实现系统调用
在本案例研究中,我们选择将 ESP RainMaker 代理安装在受保护的应用程序中。这种情况下,我们必须为 ESP RainMaker 提供的所有公共 API,增加一个自定义系统调用。得益于 ESP 特权隔离机制非常易于扩展,因此增加这样一个自定义系统调用并不复杂,详见这里。
接下来,我们将以 ESP RainMaker 的公开 API esp_rmaker_start() 为例,展示如何为其增加一个自定义系统调用。首先,我们需要把 ESP RainMaker 移动至受保护的应用程序中。此后,用户应用程序中所有对 esp_rmaker_start() 的调用均将通过我们增加的自定义系统调用接口完成。下图展示了这一过程:
具体过程描述如下:
a. 首先,我们需要在用户应用程序中实现一个系统调用包装程序 (wrapper),名称即为系统调用程序名加一个 usr_ 前缀。此后,这个包装程序将通过宏 EXECUTE_SYSCALL 生成一个同步异常,并通过该异常进入受保护空间。宏 __NR_esp_rmaker_start 即为构建系统生成的系统调用号。
esp_err_t usr_esp_rmaker_start(void) { return EXECUTE_SYSCALL(__NR_esp_rmaker_start); }
注意:构建系统已经将 esp_rmaker_start 关联至 usr_esp_rmaker_start,因此用户应用程序调用 esp_rmaker_start即可完成系统调用。
b. 接着,我们需要实现一个受保护应用程序的系统调用处理程序 (handler)。此后出现同步异常即可触发该处理程序,继而调用实际 API,并将错误代码返回给用户空间。
esp_err_t sys_esp_rmaker_start(void) { return esp_rmaker_start(); }
c. 为了绑定用户和受保护系统调用的实现,我们需要在 example 目录 (examples/rmaker_switch/components/rmaker_syscall/rmaker_syscall.tbl) 下创建一个自定义系统调用表。此时,我们需要定义四个属性:
- 唯一的系统调用号
- common / custom 属性标志
- 系统调用名
- 受保护的系统调用处理程序名
1289 common esp_rmaker_start sys_esp_rmaker_start
此后,构建系统将通过这个系统调用表文件,来创建一个 __NR_esp_rmaker_start 宏。这个宏可以将用户应用程序的 EXECUTE_SYSCALL 调用绑定至受保护应用程序的 sys_esp_rmaker_start。
以上示例仅用于演示,具体实现请见 rmaker_syscall。
5. 实现用户应用程序
在完成这些系统调用实现后,我们可以在用户应用程序中使用 ESP RainMaker 提供的几乎所有公共 API。目前,我们已经使用受保护应用程序中添加的服务,实现了一个 IoT 开关应用程序,详见 GitHub repo。
传统应用程序 vs. ESP 特权分离应用程序
可以看出,ESP 特权分离应用程序 bin 文件总大小与传统应用程序相仿,静态内存使用情况略有增加,但可以将单体式固件 (monolithic firmware) 划分为受保护的应用程序和用户应用程序。
在 ESP32-C3 SoC 上运行应用程序
1. ESP RainMaker 代理的初始化
首先,用户应用程序在启动代码注册 heap 和 console 后,将控制权移交给 user_main。接着,user_main 函数将初始化 ESP RainMaker 代理,创建一个 RainMaker 设备,并通过系统调用接口在受保护的应用程序中启动 ESP RainMaker 代理。受保护的应用程序管理 Wi-Fi 连接,并提供所有 RainMaker 服务。
2. ESP RainMaker 的云连接
一旦完成 Wi-Fi 配网和连接,受保护的应用程序将与 RainMaker 云建立 TLS 连接。在此之后,我们的 ESP32-C3 设备已准备好接收来自云端的事件。受保护的应用程序接收所有云事件,并根据配置触发用户空间回调。
未来计划
- 在不改变受保护应用程序(RainMaker 内核)的前提下,单独升级用户应用程序,进而针对业务逻辑变化,轻松更新业务应用程序,即使频繁更新也并不麻烦。
- 受保护的应用程序与 RainMaker 云建立 TLS 连接。如果用户应用程序崩溃,TLS 连接也不受影响,受保护的应用程序仍可以向 RainMaker 云发送实时调试信息。
总结
- 我们实现了一个由受保护应用程序提供的系统调用接口,来提供 RainMaker 服务。
- 我们集成了上游 ESP RainMaker 应用程序,且集成过程的改动需求极低。
- 我们对比了传统 ESP RainMaker 应用程序和 ESP 特权隔离应用程序的 bin 文件大小和静态内存使用情况。
我们的 ESP RainMaker 开关示例可见 ESP 权限隔离 repo,欢迎您尝试项目开发或提供意见反馈(可直接在 GitHub 上提交 issue)。