NXP’s HABv4 API documentation references a now-mitigated defect in ROM-resident High Assurance Boot (HAB) functionality present in devices with HAB version < 4.3.7. I could find no further public documentation on whether this constituted a vulnerability or an otherwise “uninteresting” errata item, so I analyzed it myself!
This post shines new light on this old vulnerability, its exploitation on affected devices, and how it has been mitigated. Upon sharing our results with NXP PSIRT, our analysis was confirmed to be consistent with a vulnerability mitigated in 2017 and the security bulletin provided directly to customers back in 2017 was made publicly accessible (to our knowledge, for the first time). The more we all collectively can learn about vulnerability patterns, the better – so I’m pleased with the outcome of this effort.
The following excerpt is reproduced from the HABv4 API Reference Manual (dated 2018), included with the Code Signing Tool. (Don’t worry, we’ll touch on what HAB and DCD are a bit later.) Upon first reading this, it was unclear to me as to whether the phrase “incorrect authentication boot flow” was intended to be read synonymously with “a security vulnerability” or instead refer to a functional defect in which devices failed to boot signed code.
“The DCD based SoC initialization mechanism should not be used once the boot process exits the ROM. The non-ROM user is required to only use the ‘Authenticate Image no DCD’ function if available, or make sure a null DCD pointer is passed as argument. Starting from HAB 4.3.7, the ‘Run DCD’ function, as well as the ‘Authenticate Image’ function called with a non-null DCD pointer, will return an error if called outside of the boot ROM. Older versions of HAB will run DCD commands if available, this could lead to an incorrect authentication boot flow.”
I turned to the upstream U-Boot codebase to seek out any corresponding changes in HAB-related code. A software mitigation for this issue was submitted to the U-Boot project by NXP and merged in upstream in commit 8c4037a0, prior to the U-Boot 2018.03 release. This commit, which rejects images containing non-NULL DCD pointers, includes the language about the risk of “an incorrect authentication boot flow” and highly recommends that this check be in place. However, commit ca89df7d effectively reverts this patch (by changing the non-NULL DCD pointer check from an error to a warning) due to its potential to be a “breaking change” for users that have already deployed signed firmware, with the author citing a lack of prior guidance regarding the IVT’s DCD field. As a result, the mitigation was not included in an upstream U-Boot release until 2019.04 (a year later!) where commit b2ca8907 re-introduced the non-NULL DCD requirement. Again, although references were made to documentation indicating that this check should be included to avoid “an incorrect authentication boot flow”, no discussion of this logic serving to mitigate a security vulnerability, as opposed to a functional defect, appeared to be present.
Neither official documentation nor forum posts seemed to shed light on whether there was truly a vulnerability here, so I decided to dive in further using an i.MX6ULL development kit that ships with U-Boot 2016.11 (i.e. without the upstream fixes). This particular SoC contains HAB version 4.2 in its ROM, and thus would be affected by the documented issue.
NXP i.MX 6/7/8M Application Processors (AP) provide High Assurance Boot (HAB) functionality to protect the integrity and authenticity of the first boot loader stage retrieved from non-volatile storage. ROM-resident code at documented locations export HAB API functions, allowing successive boot stages to leverage ROM-based authentication functionality when extending the hardware-backed root of trust up through OS execution.
The cryptographically signed image format used by HAB-enabled NXP i.MX Application Processors is depicted in the high-level diagram included below. More detailed information can be found in the “Program Image” section of an i.MX AP’s corresponding Reference Manual (for example, Section 8.7 of IMX6ULZRM Rev 0). The details of which sections and fields are covered by a cryptographic signature, as well as when they are processed versus authenticated, is quite nuanced and therefore not summarized in the diagram. Multiple image layout examples can be found in AN4581 (requires login). Additional discussion can be found in the HABv4 RVT Guidelines and Recommendations application note (AN12263 – requires login), processors’ Security Reference Manuals (SRMs), as well as the user guide included with NXP’s Code Signing Tool.
The Device Configuration Data (DCD) image section, along with Command Sequence File (CSF) section, contain higher-level operations (“commands”) executed by the boot ROM to perform device configuration (e.g. DDR controller initialization) and image authentication, respectively. Although they serve different purposes, the command structure, parsing logic, and function handler dispatch code within the ROM appear to be common to both.
The signature validation of the DCD and CSF sections occurs after (a subset of) their execution. I speculate that this behavior, inconsistent with modern security best practice, was necessary to support customer use-cases (perhaps in earlier chipset generations) in which an image larger than the available OCRAM had to be loaded into DDR memory before authentication could be performed. (A more recent alternative solution uses small U-Boot SPL images that can fit into OCRAM which can bootstrap a much larger U-Boot “Proper.”) As such, DCD commands to read, poll, and write to configuration register spaces are executed before there is an opportunity to authenticate them. Similarly, portions of a CSF responsible for loading certificates and SRK tables are executed before the authentication operations (each their own command in the CSF) can be performed.
When executing the first ROM-resident loader, an allow-list of memory-mapped register ranges is enforced when executing DCD commands. This mechanism restricts memory write accesses to peripheral register regions deemed strictly necessary to support boot-time configuration. The allow-list also includes the “user” portion of OCRAM (i.e., that not used by the ROM) and DDR memory for a second stage loader to be deployed. The DCD itself is copied to ROM-reserved OCRAM, and therefore is not self-modifiable. The same is true of the CSF, which generally contains an operation to authenticate itself prior the authentication of the rest of the image.
In order to support successive boot stages in extending the hardware-backed root of trust up through the execution of application software, NXP i.MX devices export HABv4 API functions at documented memory locations. For example, the U-Boot bootloader leverages this for its hab_auth_image
command implementation, commonly used to authenticate boot-time assets such as a U-Boot Proper (from an SPL), the Linux kernel, one or more Device Tree binaries, or compressed ramdisk images loaded as part of “bootcmd” sequences. A general secure boot flow is shown below.
However, when using the HAB API from a second-stage loader (e.g., U-Boot), the ROM’s allow-list is insufficient to mitigate risks arising from maliciously modified DCD and CSF image regions; the allow-list permits writes to the very OCRAM and/or DDR regions that the second stage loader is executing from. As a result, it is possible to tamper with DCD and CSF files in a manner that modifies the currently executing second stage loader to suppress authentication failure handling logic and insert unauthorized code. I regard this as two separate vulnerabilities – one for DCD regions and one for CSF regions – and describe each in more detail in the following sections.
In order to exploit both vulnerabilities, an attacker would require write access to non-volatile (NV) storage (e.g., eMMC, NAND). This could be achieved either through physical access to a platform or through local access with sufficient privilege (e.g., tethered root) to perform the requisite NV storage write operations.
Consider a U-Boot SPL or Proper image relying upon the HABv4 API to authenticate a kernel. In this use case, NXP intends for the image DCD pointer to be NULL in the image; at this point in execution, the secondary loader(s) are fully capable of performing any requisite configuration, so the use of DCD to do so would be redundant. However, if an attacker tampers with an image to insert a DCD, malicious operations executed by the ROM-resident HABv4 API code will take effect before the HABv4 API returns an authentication failure status back to the RAM-resident second stage loader. During execution of the malicious DCD, the second stage loader can be patched to ignore an authentication failure or to execute custom code elsewhere.
For example, an attacker may seek to leverage DCD modifications to patch U-Boot’s authenticate_image
function (renamed to imx_hab_authenticate_image
in U-Boot >= 2018.03) to always return success. In practice, however, the state of icache can interfere with this approach. As a proof-of-concept, I instead confirmed the vulnerability by patching entries in U-Boot’s command handler table for operations executed following an authentication failure.
The following bootcmd snippet, representative of those observed in fielded products, attempts to authenticate an image, and reboots the device upon encountering an authentication failure. (Note that hab_auth_img originally returned 1 for success; this was changed in later U-Boot versions to be more consistent with 0=success conventions.)
hab_auth_img $img $ivt_off || run boot_img $img; reset
Thus, control can be hijacked either by having the ROM’s DCD parser tamper with a function pointer in U-Boot’s command table or patching the do_reboot() implementation to simply return and fail open into a console. The former can be used to jump to code deployed elsewhere in memory, while the latter is simpler if an otherwise inaccessible console environment contains permissive operations useful to an attacker.
Below is a Ghidra screenshot depicting the “reset” command table entry within a signed U-Boot image.
The commented hex dump that follows contains the DCD operation that replaces the do_reset function pointer with the address of custom code included the payload.
Finally, the remainder of the DCD, included below, deploys a simple executable payload that prints a message and returns (i.e. “fails open”) to the U-Boot console. Thus, when authentication fails and the aforementioned bootcmd string runs the “reset” command, the payload is instead executed.
Execution of the proof-of-concept exploit is shown below:
As mentioned in passing a few times, the mitigation for this vulnerability is to enforce the requirement that the DCD pointer is NULL when the ROM-resident HAB API is called outside of the boot ROM – i.e., from a second- or third-stage loader. The U-Boot patches created by NXP implement this enforcement by adding logic before the HAB image authentication operation is invoked. This logic checks an image for its DCD pointer value and fails out with an error if a non-NULL value is observed. Documentation suggests that newer chipset versions contain an updated ROM-resident HAB library (>= version 4.3.7), which also implements this check. Nonetheless, I would recommend keeping the software-level mitigation in place just as a matter of defense-in-depth; for a modern U-Boot version, the check is already implemented so it’s no work to keep it as-is.
Although DCD and CSF sections serve fundamentally different purposes, they share a common Type-Length-Value (TLV) command scheme, and unsurprisingly, common parsing and function handler dispatch logic. Until Code Signing Tool version 2.3.3 (dated 11/14/2017), it appears that the following operations were permitted in the INI-esque source representation of CSF sections:
Of course, in ROMs supporting the above operations within a CSF, it remains possible to manually craft CSF command sequence to execute these operations, despite newer Code Signing Tool refusing to generate these now-deprecated CSF commands when it parses the INI file representation of a CSF.
These commands, most notably “Write Data”, permit a nearly identical authentication bypass methodology as the one previously described. However, instead of inserting a DCD into a signed image, an attacker can modify the CSF to include the “Write Data” command. My strategy for a proof of concept was to append the binary payload to an image and patch the do_reset function pointer in the second-stage loader. Again, by the time control returns back to the second-stage loader, the OCRAM or DDR-resident bootloader code that would be responsible for handling an authentication failure will already have been modified by the maliciously crafted CSF.
Note that within the same U-Boot patch set noted earlier, NXP introduced a software-based mitigation that scans a CSF for the above deprecated operations and rejects an image if the deprecated operations are found. This patch is available in U-Boot commit 20fa1dd3, which was included in the U-Boot 2018.03 release. Due to time limitations, I have not confirmed that the “deprecated” CSF commands are now rejected by HAB >= 4.3.7. As such, I would again recommend keeping the software-level mitigation in place.
I was certain that exploitable vulnerabilities were associated with this known issue, but still did not know whether NXP and its customers had treated this as a high impact boot-time security risk. Out of an abundance of caution, I reached out to NXP PSIRT with a draft technical advisory, per the “Vendor Communication” timeline in the following section.
From my correspondence with NXP PSIRT, I learned that this had indeed been treated as a security risk back in 2017, with affected customers being sent a security bulletin. Upon our request for access to this bulletin, NXP made this document public. It can now be found here (provided that one first creates an account on the NXP web site and agrees to the site EULA).
In general, the NXP support channel can be used to assist customers in acquiring any necessary security collateral.
As indicated by PSIRT and the security bulletin, NXP had created patches in its U-Boot forks for customers using their board support package (BSP) releases. These patches were included in the L4.9.88_2.0.0-ga release onward. Below are links to the patches in NXP’s U-Boot fork.
For customers using earlier BSP releases, backported Yocto patches were also made available:
No CVEs or other vulnerability identifiers have been allocated by NXP for these issues.
2022-08-18 – Draft advisory submitted to NXP PSIRT per coordinated disclosure process.
2022-08-18 – NXP PSIRT acknowledges receipt of advisory.
2022-08-23 – NXP PSIRT indicates these issues were identified and fixed in GA releases in 2017, providing links to publicly accessible patches. NXP also indicates a security bulletin was released and that customers were notified at the time the issue was identified.
2022-08-24 - NCC Group requests security bulletin and vulnerability identifiers. NCC Group indicates intent to publish blog post covering both technical details and dissemination of mitigations into software ecosystems.
2022-08-26 – NXP PSIRT posts public version of security bulletin, provides this link to NCC Group, and answers NCC Group’s vulnerability identifier questions.
2022-08-29 – NCC Group acknowledges access to newly created public version of bulletin, inquires if NCC Group blog post can now be posted.
2022-09-02 – NXP PSIRT indicates that NCC Group may create a public document and requests to review a copy prior to publication.
2022-09-21 – NCC Group sends NXP PSIRT a blog post draft.
2022-09-30 - NXP PSIRT returns blog post feedback and minor correction.
By studying this older security vulnerability, we’ve had an opportunity to think about interesting circumstances that can arise when one boot stage leverages functionality provided by a prior boot stage. In particular, when ROM-resident code is shared between boot stages, it is important to bear in mind which boot stage the device is currently operating in. Based upon this context, the domain of accessible assets, permissible operations, and memory-mapped accesses may need to be further restricted.
However, one open question continues to linger in my mind: How many fielded devices are affected by this vulnerability and lack mitigations? I doubt I’ll ever find an answer but speculate that there are at least few products out there. (Hopefully if there are, they’ll pass by one of our desks during a security audit so we can check for it and recommend a fix.)
This question is not intended to cast doubt on NXP’s customer communication, but rather comes to mind due to the sheer complexity of embedded system supply chains. If we assume that every affected customer acknowledged receipt of the 2017 security bulletin, there are quite a few other communication channels that can break down. For example, a vendor selling their branded product may have purchased COTS modules to integrate into their product, adding only their own application software. They are not necessarily NXP customers, and therefore would be relying on one or more OEMs to supply updates for vulnerability mitigations. Even within organizations, it can be a challenge for information to propagate effectively from one engineering team to another. All this is to say, we frequently encounter unpatched systems with clients being unaware of vulnerabilities, and communication breakdowns can be just one of many reasons. I wouldn’t be surprised if you could point me to a device lacking mitigations for a 5-year-old vulnerability.
Thank you to Jeremy Boone, Jennifer Fernick, and Rob Wood for their always-appreciated, invaluable guidance and support. Gratitude is also extended to NXP PSIRT for their support and responsiveness.