Summary
An attacker with physical access or root-level access on a system that uses the Nuvoton BootBlock first-stage bootloader can modify the u-boot image parsed by BootBlock such that it overwrites BootBlock in SRAM. By doing so, they can gain arbitrary code execution directly after the image is loaded, fully compromise the system and bypass image signature verification. The vulnerable code is in the BOOTBLOCK_CheckImageCopyAndJump function, which trusts the load address "header.destAddr" of the u-boot image header. When destAddr points into SRAM, the memcpy call in Line 220 will overwrite BootBlock and when memcpy returns, attacker code is executed.
Note that this vulnerability exists in Nuvoton's open source BootBlock project (CVE-2024-38433). It affects implementations such as Dell iDRAC, which reuse the vulnerable code. For more information, please refer to Dell's advisory.
Severity
Critical - Allows attacker with root-level access to gain arbitrary code execution.
Proof of Concept
To ensure reproducibility, our proof of concept exploit is based on the NPCM750 Evaluation Board since it is the only platform for which an entire openBMC distribution can be built without relying on proprietary patches. The necessary steps to reproduce our exploit are described in the following:
$ git clone https://github.com/openbmc/openbmc
$ cd openbmc
$ . ./setup evb-npcm750 build_poleg
# build any target that includes u-boot:
$ bitbake core-image-full-cmdline
# ...
# copy the resulting flash image
$ cp tmp/deploy/images/evb-npcm750/core-image-full-cmdline-evb-npcm750-<...>.static.mtd bmc-image-POC
Execute the proof-of-concept exploit script, passing in the flash image as a parameter:
$ python3 rootblock_nuvoton_poc.py bmc-image-POC
using input file bmc-image-POC
dst addr: 0xfffde330, code_sz: 0x8
writing patched image to: bmc-image-POC-patched.bin
Run the patched image in Qemu and attach your debugger:
$ qemu-system-arm -machine quanta-gsj -nographic -drive file=bmc-image-POC-patched.bin,if=mtd,bus=0,unit=0,format=raw -s -S
# in a separate shell, start and attach gdb:
$ gdb
(gdb) target remote localhost:1234
(gdb) hbreak * 0xfffde530
Hardware assisted breakpoint 1 at 0xfffde530
(gdb) c
Continuing.
Thread 1 hit Breakpoint 1, 0xfffde530 in ?? ()
(gdb) x/3i $pc
=> 0xfffde530: movw r0, #4919 ; 0x1337 <- Our Shellcode!
0xfffde534: bx r0
0xfffde538: ldrb r1, [r4, #320] ; 0x140
(gdb) stepi
0xfffde534 in ?? ()
(gdb)
0x00001336 in ?? ()
(gdb) p $pc
$1 = (void (*)()) 0x1336 <- we control PC
In the last step, we set a breakpoint at 0xfffde530 - this is the location that the vulnerable memcpy call returns to once it has finished copying the u-boot image. As can be seen in Line 17 of our exploit script, the mov r0, 0x1337, bx r0 instructions are actually part of our attacker-controlled shellcode and it can be seen that the processor will execute them after returning from memcpy. In a real attack scenario, an attacker would want to load their own bootloader and manipulate the system from there.
Using this to execute an unsigned bootloader has been left out intentionally, as an exercise for the reader.
Additional Analysis
The Poleg NPCM7xx SoCs have an internal SRAM block and various interfaces to external memory. In order to set up DRAM and load the next boot stage, an open source first-stage bootloader called BootBlock is loaded from an external SPI flash IC by the Boot ROM. In some configurations, the Boot ROM will verify the signature of BootBlock before executing it.
Once it executes, BootBlock initializes DRAM and various other, potentially board-specific peripherals. Most importantly, it tries to identify an u-boot image in the external SPI flash at either one of two predefined locations. A valid u-boot image is identified by its header’s magic number and its 4-byte-aligned destination address. If any valid image is found, BootBlock immediately attempts to load and execute it.
DEFS_STATUS BOOTBLOCK_CheckImageCopyAndJump (BOOT_HEADER_T *uBootHeader, BOOLEAN sigCheck, UINT8 image, BOOLEAN bJump)
{
// ...
// CheckUbootHeader performs only a minimal sanity check on the header's magic number and checks whether the destination address is not 0 and alignd to 4 byte boundaries
result = BOOTBLOCK_CheckUbootHeader_l(uBootHeader, image);
// ...
/*---------------------------------------------------------*/
/* Copy the image and the header to destination address: */
/*---------------------------------------------------------*/
if (uBootHeader->header.destAddr != 0)
{
UINT32 i;
UINT32 print_errors;
UINT32 repeat = 0;
for (repeat = 0 ; repeat < 2 ; repeat++)
{
// no bound checks are performed on header.destAddr:
UINT32* dest = (UINT32 *)(uBootHeader->header.destAddr);
UINT32* source = (UINT32 *)uBootHeader;
UINT32 size = (uBootHeader->header.codeSize + sizeof(BOOT_HEADER_T));
memcpy(dest, source, size); // vulnerable memcpy
serial_printf(KNRM "\n>copied uboot image to %#lx, size %#lx, from %#lx \n",
(UINT32)uBootHeader->header.destAddr, (UINT32)size, (UINT32)source);
Figure 1: Vulnerable Source Code
The function that contains the vulnerability is BOOTBLOCK_CheckImageCopyAndJump and a shortened version of it is shown in Figure 1. Once a valid image header is identified, it will try to load it from external, untrusted memory without verifying the destination address that is encoded in the image header at offset 0x140.
In a genuine setup, this address would point into DRAM and BootBlock would execute u-boot from there. After copying the u-boot image to DRAM, BootBlock compares the copied values to the image payload on the external flash, presumably to avoid data corruption issues. If that check succeeds, BootBlock will try to execute the loaded image.
In an attack scenario, attackers would modify the image header and add a payload to the image data so that BootBlock overwrites itself and the call to memcpy returns to attacker-controlled code. This is illustrated in Figure 2: The boot ROM first loads an authentic BootBlock image from the SPI flash, verifies it and executes it (Steps 1 and 2). Then, BootBlock discovers the malicious u-boot image and reads it (step 3). During the read, it overwrites itself because the destination address points into SRAM (step 4). In this case, the attackers patched the code that follows right after memcpy returns (step 5).
Please note that this does not require modifying the signed BootBlock image and the attack solely relies on modifying the u-boot image.

Rationale
We believe that the identified vulnerability has a “Critical” severity since BootBlock is used as a first stage bootloader and root of trust in many systems, most importantly BMCs that use the NPCM750G SoC. Most importantly, when these systems use secure boot, there are many ways in which attackers can use this vulnerability to bypass signature verification. BootBlock is a critical part of the trusted computing base: if it is compromised, attackers can compromise all subsequent boot stages and bypass any other security measures. Since there is no evidence of which code was executed during boot (such as in measured boot setups), attackers can exploit this vulnerability in a way that is undetectable by subsequent boot stages.
To protect against potential time-of-check, time-of-use (TOCTOU) attacks, most vendor implementations seem to perform the u-boot image verification after loading the u-boot image into memory. Therefore, attackers can not only gain arbitrary code execution but also bypass the image verification checks and use arbitrary payloads. Outside of this scenario, there are several other aspects that can help exploit this vulnerability:
- Even if vendor implementations first verify the image header, attackers may perform a TOCTOU attack on the image destination address since the image is typically located in an external SPI flash device and the destination address is read several times. This means it is not enough to verify the header in BOOTBLOCK_CheckUbootHeader_l, the address needs to be bound-checked every time it is read from untrusted memory
- Even if the u-boot image data is verified before loading, attackers may be able to use return-oriented-programming or code-reuse attacks by skillfully aligning an authentic image in SRAM. It is therefore not enough to verify the u-boot image data, the image header also needs to be verified.
To exploit this vulnerability, attackers only need to be able to modify the contents of the boot SPI flash. There are several ways to achieve this and each system might require a different, but similar approach:
- Use physical access to program the SPI flash
- Use root-level access on the Poleg SoC to flash malicious data to the SPI flash. This can be achieved by authenticated attackers or by attackers who abuse vulnerabilities in the software that runs on the Poleg SoC.
- Roll back to vulnerable firmware using a previously signed (but vulnerable) BootBlock image and pre-compromise the system with an image that pretends to accept updates but actually doesn’t and instead infects subsequent boot stages
Recommendations
- [RB-R-1] Introduce bound checks that verify the destination address and length values each time they are read from untrusted memory. The checks need to ensure that it is not possible to create an image that overlaps with BootBlock’s location in SRAM
- [RB-R-2] Consider making SRAM (or space occupied by BootBlock) read-only before loading images. It is unclear whether this is an option that is feasible for all use cases but we generally recommend it as a hardening measure
- [RB-R-3] In future hardware revisions, ensure that the hardware generates tamper-resistant evidence of which code was executed during boot. Combined with a unique, cryptographic identity it may be possible to create a measured boot setup. Standard solutions like DICE or MARS exist, which could address this requirement. Such a feature could help prevent key revocation and/or in-field fuse changes whenever such vulnerability is found
- [RB-R-3-1] If not feasible, create reference designs that leverage external root of trust chips in order to generate code measurements and provide cryptographic identity.
- [RB-R-4] In future silicon revisions, introduce a hardware security feature that allows implementing rollback protection without requiring key revocation
Timeline
Date reported: 03/20/2024
Date fixed:
Date disclosed: 06/24/2024
Summary
An attacker with physical access or root-level access on a system that uses the Nuvoton BootBlock first-stage bootloader can modify the u-boot image parsed by BootBlock such that it overwrites BootBlock in SRAM. By doing so, they can gain arbitrary code execution directly after the image is loaded, fully compromise the system and bypass image signature verification. The vulnerable code is in the BOOTBLOCK_CheckImageCopyAndJump function, which trusts the load address "header.destAddr" of the u-boot image header. When destAddr points into SRAM, the memcpy call in Line 220 will overwrite BootBlock and when memcpy returns, attacker code is executed.
Note that this vulnerability exists in Nuvoton's open source BootBlock project (CVE-2024-38433). It affects implementations such as Dell iDRAC, which reuse the vulnerable code. For more information, please refer to Dell's advisory.
Severity
Critical - Allows attacker with root-level access to gain arbitrary code execution.
Proof of Concept
To ensure reproducibility, our proof of concept exploit is based on the NPCM750 Evaluation Board since it is the only platform for which an entire openBMC distribution can be built without relying on proprietary patches. The necessary steps to reproduce our exploit are described in the following:
Execute the proof-of-concept exploit script, passing in the flash image as a parameter:
Run the patched image in Qemu and attach your debugger:
In the last step, we set a breakpoint at 0xfffde530 - this is the location that the vulnerable memcpy call returns to once it has finished copying the u-boot image. As can be seen in Line 17 of our exploit script, the mov r0, 0x1337, bx r0 instructions are actually part of our attacker-controlled shellcode and it can be seen that the processor will execute them after returning from memcpy. In a real attack scenario, an attacker would want to load their own bootloader and manipulate the system from there.
Using this to execute an unsigned bootloader has been left out intentionally, as an exercise for the reader.
Additional Analysis
The Poleg NPCM7xx SoCs have an internal SRAM block and various interfaces to external memory. In order to set up DRAM and load the next boot stage, an open source first-stage bootloader called BootBlock is loaded from an external SPI flash IC by the Boot ROM. In some configurations, the Boot ROM will verify the signature of BootBlock before executing it.
Once it executes, BootBlock initializes DRAM and various other, potentially board-specific peripherals. Most importantly, it tries to identify an u-boot image in the external SPI flash at either one of two predefined locations. A valid u-boot image is identified by its header’s magic number and its 4-byte-aligned destination address. If any valid image is found, BootBlock immediately attempts to load and execute it.
Figure 1: Vulnerable Source Code
The function that contains the vulnerability is BOOTBLOCK_CheckImageCopyAndJump and a shortened version of it is shown in Figure 1. Once a valid image header is identified, it will try to load it from external, untrusted memory without verifying the destination address that is encoded in the image header at offset 0x140.
In a genuine setup, this address would point into DRAM and BootBlock would execute u-boot from there. After copying the u-boot image to DRAM, BootBlock compares the copied values to the image payload on the external flash, presumably to avoid data corruption issues. If that check succeeds, BootBlock will try to execute the loaded image.
In an attack scenario, attackers would modify the image header and add a payload to the image data so that BootBlock overwrites itself and the call to memcpy returns to attacker-controlled code. This is illustrated in Figure 2: The boot ROM first loads an authentic BootBlock image from the SPI flash, verifies it and executes it (Steps 1 and 2). Then, BootBlock discovers the malicious u-boot image and reads it (step 3). During the read, it overwrites itself because the destination address points into SRAM (step 4). In this case, the attackers patched the code that follows right after memcpy returns (step 5).
Please note that this does not require modifying the signed BootBlock image and the attack solely relies on modifying the u-boot image.
Rationale
We believe that the identified vulnerability has a “Critical” severity since BootBlock is used as a first stage bootloader and root of trust in many systems, most importantly BMCs that use the NPCM750G SoC. Most importantly, when these systems use secure boot, there are many ways in which attackers can use this vulnerability to bypass signature verification. BootBlock is a critical part of the trusted computing base: if it is compromised, attackers can compromise all subsequent boot stages and bypass any other security measures. Since there is no evidence of which code was executed during boot (such as in measured boot setups), attackers can exploit this vulnerability in a way that is undetectable by subsequent boot stages.
To protect against potential time-of-check, time-of-use (TOCTOU) attacks, most vendor implementations seem to perform the u-boot image verification after loading the u-boot image into memory. Therefore, attackers can not only gain arbitrary code execution but also bypass the image verification checks and use arbitrary payloads. Outside of this scenario, there are several other aspects that can help exploit this vulnerability:
To exploit this vulnerability, attackers only need to be able to modify the contents of the boot SPI flash. There are several ways to achieve this and each system might require a different, but similar approach:
Recommendations
Timeline
Date reported: 03/20/2024
Date fixed:
Date disclosed: 06/24/2024