本文将讨论在连接的设备中更新引导加载程序的问题。所讨论的原则适用于任何软件系统,我们将专门讨论运行 Linux 的系统。
现代电子设备越来越复杂,并且互联网连接。作为一般规则,复杂性与安全性背道而驰,不安全的互联网连接设备已经成熟,罪魁祸首会被滥用。在设计这些系统时,我们必须假设所有软件都会有错误,其中一些错误将是可利用的漏洞。解决这些问题的第一步是确保软件更新可以交付到您的系统,最好是自动和无线 (OTA)。欧盟“消费者物联网网络安全:基线要求(ETSI EN 303 645)”标准草案特别将及时自动更新作为其要求之一。它确实为不可变的第一阶段引导加载程序提供了一个例外,以最大程度地降低将设备留在现场处于非引导状态的风险(也称为“砖块”“板)。
本文将讨论在连接的设备中更新引导加载程序的问题。请注意,虽然这里讨论的原则适用于任何软件系统,但我们将专门讨论运行 Linux 的系统。使用更小、更自定义设计的系统可能会提供更多这些系统独有的选项。
系统设计
图 1 显示了一个通用的 Linux 系统,其中包含可能更新的主要组件。存储介质将是某种块设备,例如 eMMC 或 SATA 硬盘驱动器。在该设备中,将有引导加载程序、内核、设备树(取决于正在使用的 CPU)和一个根文件系统,其中包含构建系统所需的所有文件。在某些情况下会使用更复杂的架构,但出于本讨论的目的,我们将它限制在最简单的情况下。
系统更新实用程序,如Mender[2],软件更新[3],其他人能够开箱即用地更新内核、设备树和根文件系统,在许多情况下,这种级别的可更新性就足够了。
引导加载程序是系统的组件,负责在开机时初始化系统,从 CPU 重置指令开始。它负责以下任务:
?初始化和清理内存
?将所有外围设备设置为已知和静止状态,以避免意外中断。
?加载并启动 Linux 内核
如前所述,所有软件都有错误,因此我们可以假设也会有引导加载程序错误。我们可以通过最小化引导加载程序的功能来减少攻击面,但是我们可以完全消除错误的风险。为什么更新引导加载程序比更新系统的其他组件更复杂?如果我们尝试,会有什么风险?如果我们不尝试,会有什么风险?
支持无线的系统
此框图显示了能够进行可靠的无线 (OTA) 更新的系统的基本系统设计。[4]引导加载程序负责系统初始化并与 OTA 客户端交互,以选择要使用的内核、设备树和根文件系统。通过对正在运行的 Linux 映像所需的组件进行完全冗余来提供健壮性。这可确保在 OTA 更新损坏的情况下始终有已知良好的映像要回滚。此外,这可确保完全原子更新,因为更新客户端是系统中唯一知道更新正在进行中的组件,直到更新完成并准备好运行。
任何更新 OTA 的组件都可能导致设备无法正常工作,因此系统的稳健性与引导加载程序处理回滚到先前已知良好的配置的能力直接相关。这意味着系统中必须有一个组件,该组件是不可变的,可以正确处理错误的更新。
引导加载程序更新
在大多数情况下,处理回滚的不可变组件_是_引导加载程序。在典型的嵌入式Linux应用程序中是Das U-Boot[5]。如果我们尝试更新引导加载程序,由于没有冗余,我们就有使主板变砖的风险。如果开发板在我们开始写入新的引导加载程序映像之后,但在写入完成之前重新启动,则我们的映像包含旧版本的一部分和新版本的一部分。在这种情况下,行为是未定义的,唯一的缓解措施是能够物理访问设备,以便编写正确的引导加载程序,通常使用 USB 或其他硬连线连接。
但是我们为什么要更新引导加载程序呢?至少,引导加载程序只是用作初始化硬件的一种手段,然后将控制权传递给 Linux 内核。由于功能有限,引导加载程序出现问题的风险被降至最低。
对于许多设计来说,这种风险水平是可以接受的,架构师可以决定不在其部署的设备中提供OTA引导加载程序更新。使用硬连线机制仍然是最后的手段。
然而,对于许多设计,这种风险水平被认为是不可接受的,必须为引导加载程序的OTA更新提供一些机制。此外,许多设计在引导加载程序中添加了更多功能;诸如系统诊断或其他特定于应用程序的要求之类的内容可能会在引导加载程序中实现,从而导致需要更新的可能性更大。那么我们如何处理呢?
用于提供引导加载程序更新的选项
有许多选项允许更新引导加载程序。本讨论并非旨在提供完整的解决方案,而是对可能适用于您的设计的方法进行高级描述。每个都有其权衡。
选项 1:无冗余
如果砖砌板的风险对于特定应用程序来说是可以接受的,那么您可以简单地尝试部署引导加载程序更新 OTA,并在发生时处理后果。如果您的队列规模较小,并且物理访问设备的成本较低,那么这可能效果很好。如果需要引导加载程序更新,并且 OTA 尝试失败,那么您的尝试也不会更糟。OTA 引导加载程序更新失败的情况与没有 OTA 引导加载程序更新功能的情况相同。即,您必须获得对设备的物理访问权限,并使用制造商提供的机制来重新刷新引导加载程序。
选项 2:多阶段引导加载程序
此体系结构将引导加载程序功能分为两个阶段(或更多阶段,具体取决于设计的复杂性)。最终,这仍然需要在阶段 1 中提供一段不可变的代码。您在更新阶段 2 时确实具有冗余和健壮性,因此如果您仔细选择实现功能的位置,则可以提供引导加载程序功能的 OTA 更新。这是一个不错的选择,因为不可变阶段 1 二进制文件中的代码量减少了,从而降低了总体风险。
U-Boot 使用 SPL(辅助程序加载器)和 TPL(第三程序加载器)实现多阶段引导。引入此机制是为了允许支持具有单独引导 ROM 的系统,这些引导 ROM 太小而无法存储完整的 U-Boot 映像。在这种情况下,U-Boot SPL 映像将包含足够的初始化代码来加载和启动完整的 U-Boot 映像,通常是从大型块设备(如 MMC)启动。SPL 需要能够初始化足够的 RAM 和包含完整 U-Boot 映像的设备。
即使对于没有小引导ROM限制的设备,我们也可以利用这种架构在第2阶段实现我们的可更新功能,同时在第1阶段保留最低限度,包括正确处理冗余块。
第 1 阶段存在问题的风险,需要物理访问才能解决。鉴于第 1 阶段的功能减少,在许多情况下,这种风险水平是可以接受的。
选项 3:并行引导加载程序
许多主板提供从多个设备启动的功能。例如,许多主板可以从板载 eMMC 或可移动 SD/MMC 卡启动。或者,他们可以为引导加载程序使用专用的 NOR 闪存设备,但仍能够在 eMMC 块介质之外运行引导加载程序。
这些类型的主板可以配置为将不可变引导加载程序存储在其中一个受支持的设备中,然后将 OTA 可更新引导加载程序存储在另一个设备中。通常,可更新的引导加载程序将与根文件系统位于同一介质(即eMMC)中,因此很容易更新。由于“备用”媒体中的引导加载程序是不可变的,因此可以依靠它从“标准”位置引导加载程序的损坏 OTA 更新中恢复。
这种方法的问题在于,引导设备的选择通常需要物理访问电路板才能移动跳线或更改开关设置。如果您的设备位于最终用户可以访问它们的位置,这可能是一个可行的选项,因为最终用户可以在发生故障时选择恢复媒体。这可以通过文档或支持人员的指示完成。
某些系统使用外部硬件来选择引导加载程序。运行RTOS的小型MCU可以监控正确的系统活动,并在Linux系统未运行时选择备用引导加载程序。使用外部源正确检测可能很棘手,但切换 GPIO 引脚或写入共享内存的看门狗计时器可能就足够了。这也是一个更复杂的设计,需要根据您的系统要求进行考虑。请注意,您可能需要考虑对MCU固件映像进行OTA更新,这是另一个复杂程度。
选项 4:eMMC 启动分区
eMMC 的 4.3 版[6]规范需要 2 个单独的硬件引导分区。这些分区通常每个为 4MB,用于存储引导加载程序。这些分区可以从 Linux 用户空间读取和写入,但默认情况下它们是只读的;读写功能是通过写入 /sys 伪文件系统中的文件来启用的:
然后可以使用dd实用程序将引导加载程序写入这些分区
eMMC 设备用作启动块的分区由设备本身内设置的参数确定。这可以从 U-Boot 提示符下完成:
或者从 Linux 用户空间:
曼德的方法
利用 eMMC 引导分区,对分区的更新是原子的,并且独立于对根文件系统的更新。eMMC 启动分区之间没有自动故障转移,因此这不会减轻由于启动加载程序更新失败而导致砖块设备的担忧。但是,这确实可以轻松向引导加载程序提供更新,而无需为根文件系统提供任何特定的调整。
由于提供引导加载程序更新时存在砖砌板的风险和 OTA 更新过程的鲁棒性降低,Mender[7]不提供现成的引导加载程序更新。如前所述,很难以通用方式完成,并且最终可能会非常特定于应用程序和硬件。更新模块框架[8]允许插件架构支持自定义更新类型。Mender 可以使用自定义更新模块支持任意有效负载类型。此插件架构允许提供处理特定有效负载类型的自定义脚本。允许在特定系统中进行引导加载程序更新可以使用更新模块来实现。根据应用程序的需求以及正在使用的硬件的功能,可以使用上述任何方法。
总结
在现场部署的设备中上传系统引导加载程序存在许多风险。在不合时宜的时间断电可能会使设备在现场变砖,从而导致潜在的代价高昂的召回过程。但是,不提供引导加载程序更新机制可能会带来不可接受的风险,具体取决于特定应用程序的配置文件。我们介绍了许多允许引导加载程序更新的方法,并讨论了每种方法的优缺点。作为系统设计人员,这将有望让您为系统做出适当的选择,并帮助您在适当了解设计风险的情况下快速进入市场。
审核编辑:郭婷
-
物联网
+关注
关注
2900文章
44052浏览量
370081 -
Linux
+关注
关注
87文章
11204浏览量
208701
发布评论请先 登录
相关推荐
评论