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