14.3.4. 关于闪存读取保护的说明

出厂时,已发售的 OpenMV 摄像头上的固件对于任何能够物理接触设备的人来说都是可读取的。手持摄像头的攻击者可以将 SWD 探针连接到调试接口排针上,与 MCU 的调试接口通信,并转储闪存内容——其中包括每一个冻结的 Python 模块以及 ROMFS 分区的内容。出厂的 OpenMV 固件默认不会启用闪存读取保护。

本页面明确记录这一点,以便发布产品的团队清楚责任归属。

14.3.4.1. 摄像头默认的行为

摄像头的引导加载程序和运行时不会开启底层 MCU 的任何读取保护功能。调试接口保持开放,闪存保持可读取状态,构建运行的方式与在开发者工作台上完全一致。对于本教程的受众来说,这是正确的默认设置——出厂时启用了读取保护的摄像头将无法通过 IDE 重新烧录固件,无法在部署失败后重新写入镜像,除了构建团队之外也无人能将其修复。

当摄像头从“开发者设备”转变为“产品”时,这种权衡也随之改变。如果某个产品的价值取决于应用代码保持私密,那么它就必须自行启用保护;OpenMV 固件不会替你完成这一步。

14.3.4.2. 产品团队需要做的事

每家 MCU 厂商都提供读取保护机制。具体细节各不相同——位级熔丝、一次性的生命周期状态转换、签名闪存镜像——但其共同的模式是:

  • 将厂商特定的某一位(或一组位)写入到芯片中,通常通过厂商工具最后一次与 MCU 的调试端口通信来完成。

  • 提交之后,调试端口将拒绝读取闪存。摄像头仍可正常启动并运行应用程序;它只是不再向探针暴露其内容。

  • 该提交是不可逆的。除非销毁摄像头,否则没有任何办法将其恢复到可调试状态。

进行此设置的方式因 MCU 而异,具体步骤取决于摄像头上要保护的具体芯片。厂商的参考手册是权威依据;厂商支持是在生产线上正确完成此操作的途径。

这是简单的部分。

困难的部分在于封堵攻击者用来在摄像头上运行代码或读取应用程序运行情况的其他每一条途径。读取保护只能阻止调试探针转储闪存。摄像头仍需封堵以下途径:

  • MicroPython REPL。 通过 USB 连接的 REPL 接受任意 Python 代码。读取保护不会改变这一点。一个 REPL 会话可以读取 RAM、调用函数、窃取正在运行的应用程序能够看到的任何内容——实际上,一个可访问的 REPL 会绕过读取保护所带来的一切防护。禁用 REPL 访问是一项由产品团队负责的固件构建变更。

  • IDE 脚本上传。 IDE 的“在摄像头上运行此脚本”路径使用与 REPL 相同的 USB 协议层。关闭 REPL 即可一并关闭此功能;只要任一者保持开放,就等于为摄像头留下了一条任意代码执行通道。

  • 从文件系统劫持入口点。 当应用程序通过 将脚本冻结到固件中 发布时,此途径已经被关闭——运行时会在任何文件系统副本之前解析冻结的 boot.pymain.py,因此放置在闪存或 SD 卡上的任何内容都无法覆盖它们。一旦应用程序进入构建,这层保护便是免费的。

  • 较新摄像头上的外部闪存。 将应用程序镜像存储在外部闪存中的摄像头,会把该镜像放在 PCB 上一颗清晰可见的芯片里;这颗芯片可以被拆焊下来用现成工具直接读取,或在原位通过探测总线读取。要保护它,需要开启片上硬件以在读取时解密闪存内容、生成加密密钥、将该密钥下发到摄像头中,并将其不可逆地烧录到 MCU 的一次性可编程存储中。其中每一项都是独立的一次性操作,任何一项在量产单元上操作失误都会使该单元变砖。

此列表中的每一项都是一整套固件构建工作、生产线步骤和不可逆提交。一个真正锁定的产品意味着定制的固件构建、定制的引导加载程序、为每个单元下发密钥的生产流程,以及一套在单元离开生产线之前证明锁定确实生效的测试。这是数月而非数日的工作量,而且不可逆性意味着错误的代价是损失单元。

为何默认是开放的

这份列表也正是出厂 OpenMV 固件在发布时启用读取保护的原因。一个关闭了 REPL、禁用了 IDE 脚本上传并锁定了固件的摄像头,将完全无法在其上进行开发——使 OpenMV 摄像头之所以可用的整套工作流程将不复存在。默认设置让一切保持开放;产品团队在迈向成品单元的过程中自行选择要关闭哪些部分。

14.3.4.3. 物理接触仍然能做到什么

即使启用了读取保护,手持摄像头的攻击者仍能做不少事情:

  • 通过嗅探摄像头的输出来重放其网络流量。

  • 观察其可见行为并推断它对各种输入的反应。

  • 在某些情况下,通过针对受保护 MCU 的故障注入或侧信道攻击恢复出机密信息。

读取保护提高了获取应用程序源代码的成本,但并不能消除这一成本。安全审查应当从“物理接触即等于失陷”这一基本假设出发;保护机制只是决定了这种失陷在时间和设备上的代价有多大。

14.3.4.4. OpenMV 固件出厂时的状态

归纳如下,以便更加具体:

  • 默认不启用读取保护。

  • 出厂固件中没有可用于开启它的构建标志。

  • 没有可从 MicroPython 调用的应用层 API。

需要此保护的产品会发布定制固件。这类定制存在于电路板的引导加载程序和生产流程之中,不在 OpenMV 代码库的范畴内。首次进行此项工作的团队应当将其作为一项独立工作纳入开发时间表,而不是放到最后才添加——其不可逆性使得“以后再加”代价高昂。