summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-09-04 12:22:49 -0600
committerTom Rini <[email protected]>2026-09-04 16:55:10 -0600
commit5534f2c742916efabff3e1888912dafc570f20f2 (patch)
tree1ad0e57528769aef86a2cb7e3a4297495125d255
parent7bf077ec5733a230bbce34d8ce324f92d8c46cc1 (diff)
parent3158b844335a4a84a5504a813f2daa6acff5985d (diff)
Merge patch series "fit: Harden handling of external-data properties"
Anton Ivanov <[email protected]> says: The data-offset, data-position and data-size FIT properties are 32-bit unsigned values, but were read through signed int. Also, they are excluded from the configuration signature, so they are attacker controlled. Patch 1 switches the accessors and their callers to u32, removing the ad-hoc handling of "negative" values in U-Boot proper. This transition was discussed and agreed on in [1]. The SPL loader has its own copy of this logic with the same problems and fewer checks. Patch 2 factors out a test helper, patch 3 makes the SPL offset/size arithmetic overflow-safe, and patch 4 adds the addressable-range and FIT_SIGNATURE_MAX_SIZE bounds check that U-Boot proper already performs in fit_image_get_data(). Patches 3 and 4 build on each other and on patch 1, so they are not intended to be cherry-picked individually. [1] https://lore.kernel.org/u-boot/CAPWaX55XWFLcMGRuaUuXXn__MX_UG4J8QTd6rvXZmJ4WSOCy9w@mail.gmail.com/ Link: https://lore.kernel.org/r/[email protected]
-rw-r--r--boot/image-fit.c29
-rw-r--r--common/spl/spl_fit.c122
-rw-r--r--common/splash_source.c2
-rw-r--r--drivers/fpga/socfpga_arria10.c3
-rw-r--r--include/image.h6
-rw-r--r--test/image/spl_load.c128
-rw-r--r--test/py/tests/test_vboot.py10
7 files changed, 210 insertions, 90 deletions
diff --git a/boot/image-fit.c b/boot/image-fit.c
index ef90c5abd18..26e9323da06 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -963,7 +963,7 @@ int fit_image_get_emb_data(const void *fit, int noffset, const void **data,
* 0, on success
* -ENOENT if the property could not be found
*/
-int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
+int fit_image_get_data_offset(const void *fit, int noffset, u32 *data_offset)
{
const fdt32_t *val;
@@ -988,7 +988,7 @@ int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset)
* -ENOENT if the property could not be found
*/
int fit_image_get_data_position(const void *fit, int noffset,
- int *data_position)
+ u32 *data_position)
{
const fdt32_t *val;
@@ -1012,7 +1012,7 @@ int fit_image_get_data_position(const void *fit, int noffset,
* 0, on success
* -ENOENT if the property could not be found
*/
-int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
+int fit_image_get_data_size(const void *fit, int noffset, u32 *data_size)
{
const fdt32_t *val;
@@ -1070,18 +1070,13 @@ 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;
size_t fdt_total_size_aligned;
+ u32 offset;
+ u32 len;
uintptr_t max_offset;
if (!fit_image_get_data_position(fit, noffset, &offset)) {
- if (offset < 0) {
- printf("Invalid external data position: %d\n", offset);
- return -EINVAL;
- }
-
external_data = true;
} else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
/*
@@ -1090,9 +1085,9 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
* for the data-offset properties in each image.
*/
fdt_total_size_aligned = ((fdt_totalsize(fit) + 3) & ~3);
- /* The resulting offset cannot exceed INT_MAX */
- if (offset < 0 || fdt_total_size_aligned > INT_MAX - offset) {
- printf("Invalid external data offset: %d\n", offset);
+ /* The resulting offset cannot exceed UINT32_MAX */
+ if (fdt_total_size_aligned > UINT32_MAX - offset) {
+ printf("Invalid external data offset: %u\n", offset);
return -EINVAL;
}
offset += fdt_total_size_aligned;
@@ -1106,16 +1101,12 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
max_offset = UINTPTR_MAX - (uintptr_t)fit;
/* Check that external data offset is within the addressable range */
if (offset > max_offset) {
- printf("Invalid external data offset: %d\n", offset);
+ printf("Invalid external data offset: %u\n", offset);
return -EINVAL;
}
ret = fit_image_get_data_size(fit, noffset, &len);
if (!ret) {
- if (len < 0) {
- printf("Invalid external data size: %d\n", len);
- return -EINVAL;
- }
/*
* For non-signed FIT images, we can only check that
* (offset + len) doesn't exceed the addressable range.
@@ -1135,7 +1126,7 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
len > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) - offset
#endif
) {
- printf("FIT external data is out of bounds (offset=%d, size=%d)\n",
+ printf("FIT external data is out of bounds (offset=%u, size=%u)\n",
offset, len);
return -EINVAL;
}
diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c
index 18bff7b8d4a..9da709fd79a 100644
--- a/common/spl/spl_fit.c
+++ b/common/spl/spl_fit.c
@@ -175,22 +175,32 @@ static int spl_fit_get_image_node(const struct spl_fit_info *ctx,
return node;
}
-static int get_aligned_image_offset(struct spl_load_info *info, int offset)
+static u32 get_aligned_image_offset(struct spl_load_info *info, u32 offset)
{
return ALIGN_DOWN(offset, spl_get_bl_len(info));
}
-static int get_aligned_image_overhead(struct spl_load_info *info, int offset)
+static u32 get_aligned_image_overhead(struct spl_load_info *info, u32 offset)
{
return offset & (spl_get_bl_len(info) - 1);
}
-static int get_aligned_image_size(struct spl_load_info *info, int data_size,
- int offset)
+static int get_aligned_image_size(struct spl_load_info *info, ulong data_size,
+ u32 offset, ulong *aligned_size)
{
- data_size = data_size + get_aligned_image_overhead(info, offset);
+ u32 overhead = get_aligned_image_overhead(info, offset);
- return ALIGN(data_size, spl_get_bl_len(info));
+ if (data_size > ULONG_MAX - overhead)
+ return -EOVERFLOW;
+ data_size += overhead;
+
+ if (data_size > ULONG_MAX - (spl_get_bl_len(info) - 1))
+ return -EOVERFLOW;
+ data_size = ALIGN(data_size, spl_get_bl_len(info));
+
+ *aligned_size = data_size;
+
+ return 0;
}
/**
@@ -216,24 +226,23 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
const struct spl_fit_info *ctx, int node,
struct spl_image_info *image_info, ulong max_size)
{
- int offset;
+ u32 offset;
+ u32 len;
size_t length;
- int len;
ulong size;
ulong load_addr;
void *load_ptr;
void *src;
- ulong overhead;
uint8_t image_comp = -1, type = -1;
const void *data;
const void *fit = ctx->fit;
bool external_data = false;
+ int ret;
log_debug("starting\n");
if (CONFIG_IS_ENABLED(BOOTMETH_VBE) &&
xpl_get_phase(info) != IH_PHASE_NONE) {
enum image_phase_t phase;
- int ret;
ret = fit_image_get_phase(fit, node, &phase);
/* if the image is for any phase, let's use it */
@@ -273,13 +282,19 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
if (!fit_image_get_data_position(fit, node, &offset)) {
external_data = true;
} else if (!fit_image_get_data_offset(fit, node, &offset)) {
- log_debug("read offset %x = offset from fit %lx\n",
- offset, (ulong)offset + ctx->ext_data_offset);
+ /* The resulting offset cannot exceed UINT32_MAX */
+ if (ctx->ext_data_offset > UINT32_MAX - offset) {
+ log_debug("Invalid external data offset: %u\n", offset);
+ return -EINVAL;
+ }
+ log_debug("read offset %x = offset from fit %x\n", offset,
+ (u32)(offset + ctx->ext_data_offset));
offset += ctx->ext_data_offset;
external_data = true;
}
if (external_data) {
+ u32 aligned_offset;
ulong read_offset;
void *src_ptr;
@@ -300,16 +315,26 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
* controlled even after fit_config_verify() succeeds. The
* image hash is only verified after the device read below, so
* an oversized value has to be rejected here.
- *
- * Bail out before get_aligned_image_size() runs on a hostile
- * len: that helper does its arithmetic in int and would
- * invoke signed-integer overflow on a value close to or above
- * INT_MAX. The block-aligned check further down is the
- * mathematically binding one, since size is len rounded up to
- * the device block length.
*/
- if ((ulong)len > max_size)
- goto too_big;
+ ret = get_aligned_image_size(info, len, offset, &size);
+ if (ret) {
+ log_debug("Invalid external data size: %u\n", len);
+ return ret;
+ }
+
+ if (size > max_size) {
+ log_debug("Image too large: aligned size %lu, max %lu (data-size %u)\n",
+ size, max_size, len);
+ return -EFBIG;
+ }
+
+ aligned_offset = get_aligned_image_offset(info, offset);
+ if (aligned_offset > ULONG_MAX - fit_offset) {
+ log_debug("Invalid aligned external data offset: %u\n",
+ aligned_offset);
+ return -EINVAL;
+ }
+ read_offset = fit_offset + aligned_offset;
if (spl_decompression_enabled() &&
(image_comp == IH_COMP_GZIP || image_comp == IH_COMP_LZMA))
@@ -318,18 +343,31 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
src_ptr = map_sysmem(ALIGN(load_addr, ARCH_DMA_MINALIGN), len);
length = len;
- overhead = get_aligned_image_overhead(info, offset);
- size = get_aligned_image_size(info, length, offset);
- read_offset = fit_offset + get_aligned_image_offset(info,
- offset);
-
/*
- * info->read() transfers the block-aligned size into the
- * destination, so this is the bound that actually matters;
- * len was rejected above only to keep this computation safe.
+ * For non-signed FIT images, we can check that
+ * (read_offset + size) does not wrap and that
+ * (src_ptr + size) does not exceed the addressable range.
+ * For signed FITs, we can additionally check that
+ * (offset + len) doesn't exceed the allowed FIT image
+ * maximum size.
+ */
+ if (size > ULONG_MAX - read_offset ||
+ size > UINTPTR_MAX - (uintptr_t)src_ptr
+ /*
+ * #if (not a runtime if) is required: FIT_SIGNATURE_MAX_SIZE
+ * depends on FIT_SIGNATURE, so CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE)
+ * is undefined when signing is disabled and referencing it
+ * here would fail to compile.
*/
- if (size > max_size)
- goto too_big;
+#if CONFIG_IS_ENABLED(FIT_SIGNATURE)
+ || offset > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) ||
+ len > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) - offset
+#endif
+ ) {
+ log_debug("FIT external data is out of bounds (offset=%u, size=%u)\n",
+ offset, len);
+ return -EINVAL;
+ }
log_debug("reading from offset %x / %lx size %lx to %p: ",
offset, read_offset, size, src_ptr);
@@ -339,7 +377,7 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
debug("External data: dst=%p, offset=%x, size=%lx\n",
src_ptr, offset, (unsigned long)length);
- src = src_ptr + overhead;
+ src = src_ptr + get_aligned_image_overhead(info, offset);
} else {
/* Embedded data */
if (fit_image_get_emb_data(fit, node, &data, &length)) {
@@ -401,11 +439,6 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
upl_add_image(fit, node, load_addr, length);
return 0;
-
-too_big:
- printf("%s: FIT image too large (data-size %u, max %lu)\n",
- __func__, (u32)len, max_size);
- return -EFBIG;
}
static bool os_takes_devicetree(uint8_t os)
@@ -737,8 +770,9 @@ static int spl_simple_fit_read(struct spl_fit_info *ctx,
struct spl_load_info *info, ulong offset,
const void *fit_header)
{
- unsigned long count, size;
+ unsigned long aligned_size, count, size;
void *buf;
+ int ret;
/*
* For FIT with external data, figure out where the external images
@@ -756,8 +790,12 @@ static int spl_simple_fit_read(struct spl_fit_info *ctx,
* For FIT with data embedded, data is loaded as part of FIT image.
* For FIT with external data, data is not loaded in this step.
*/
- size = get_aligned_image_size(info, size, 0);
- buf = board_spl_fit_buffer_addr(size, size, 1);
+ ret = get_aligned_image_size(info, size, 0, &aligned_size);
+ if (ret) {
+ log_debug("Invalid FIT size: %lu\n", size);
+ return ret;
+ }
+ buf = board_spl_fit_buffer_addr(aligned_size, aligned_size, 1);
if (!buf) {
/*
* We assume that none of the board will ever use 0x0 as a
@@ -767,7 +805,7 @@ static int spl_simple_fit_read(struct spl_fit_info *ctx,
return -EIO;
}
- count = info->read(info, offset, size, buf);
+ count = info->read(info, offset, aligned_size, buf);
if (!count) {
/*
* FIT could not be read. This means we should free the
@@ -800,7 +838,7 @@ static int spl_simple_fit_read(struct spl_fit_info *ctx,
ctx->fit = buf;
debug("fit read offset %lx, size=%lu, dst=%p, count=%lu\n",
- offset, size, buf, count);
+ offset, aligned_size, buf, count);
return 0;
}
diff --git a/common/splash_source.c b/common/splash_source.c
index e02f9be05e4..a5ed7431b8e 100644
--- a/common/splash_source.c
+++ b/common/splash_source.c
@@ -351,7 +351,7 @@ static int splash_load_fit(struct splash_location *location, ulong bmp_load_addr
const void *internal_splash_data;
size_t internal_splash_size;
int external_splash_addr;
- int external_splash_size;
+ u32 external_splash_size;
bool is_splash_external = false;
struct legacy_img_hdr *img_header;
const u32 *fit_header;
diff --git a/drivers/fpga/socfpga_arria10.c b/drivers/fpga/socfpga_arria10.c
index e9822b2bb0e..e397b37adf0 100644
--- a/drivers/fpga/socfpga_arria10.c
+++ b/drivers/fpga/socfpga_arria10.c
@@ -547,7 +547,8 @@ static int first_loading_rbf_to_buffer(struct udevice *dev,
u32 *loadable = buffer_p;
size_t buffer_size = *buffer_bsize;
size_t fit_size;
- int ret, i, count, confs_noffset, images_noffset, rbf_offset, rbf_size;
+ int ret, i, count, confs_noffset, images_noffset;
+ u32 rbf_offset, rbf_size;
const char *fpga_node_name = NULL;
const char *uname = NULL;
diff --git a/include/image.h b/include/image.h
index 6edcb1995bf..1de0a84007a 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1263,10 +1263,10 @@ int fit_image_get_load(const void *fit, int noffset, ulong *load);
int fit_image_get_entry(const void *fit, int noffset, ulong *entry);
int fit_image_get_emb_data(const void *fit, int noffset, const void **data,
size_t *size);
-int fit_image_get_data_offset(const void *fit, int noffset, int *data_offset);
+int fit_image_get_data_offset(const void *fit, int noffset, u32 *data_offset);
int fit_image_get_data_position(const void *fit, int noffset,
- int *data_position);
-int fit_image_get_data_size(const void *fit, int noffset, int *data_size);
+ u32 *data_position);
+int fit_image_get_data_size(const void *fit, int noffset, u32 *data_size);
int fit_image_get_data_size_unciphered(const void *fit, int noffset,
size_t *data_size);
int fit_image_get_data(const void *fit, int noffset, const void **data,
diff --git a/test/image/spl_load.c b/test/image/spl_load.c
index c43c977f784..334ec77b18e 100644
--- a/test/image/spl_load.c
+++ b/test/image/spl_load.c
@@ -368,55 +368,145 @@ SPL_IMG_TEST(spl_test_image, FIT_INTERNAL, 0);
SPL_IMG_TEST(spl_test_image, FIT_EXTERNAL, 0);
/*
- * A FIT image's data-size property is not covered by the configuration
- * signature, so it is untrusted input. load_simple_fit() must reject a
- * data-size larger than the destination rather than overrun it, because the
- * device read happens before the image hash is verified.
+ * Build a FIT with external data, overwrite one property of the image node
+ * with a hostile value and check that loading fails with the expected error.
+ * The external-data properties are excluded from the configuration signature,
+ * so load_simple_fit() must reject values that would wrap its offset/size
+ * arithmetic rather than read from a bogus location.
*/
-static int spl_test_fit_external_oversize(struct unit_test_state *uts)
+static int check_fit_ext_prop(struct unit_test_state *uts, const char *prop,
+ u32 value, uint bl_len, spl_load_reader h_read,
+ ulong fit_offset, int expected)
{
size_t img_size, img_data, data_size = SPL_TEST_DATA_SIZE;
struct spl_image_info info_write = {
- .name = "oversize",
+ .name = "ext-prop",
.size = data_size,
}, info_read = { };
struct spl_load_info load;
void *img;
int node;
- if (!image_supported(FIT_EXTERNAL))
- return -EAGAIN;
-
img_size = create_image(NULL, FIT_EXTERNAL, &info_write, &img_data);
ut_assert(img_size);
img = calloc(img_size, 1);
ut_assertnonnull(img);
- generate_data(img + img_data, data_size, "oversize");
+ generate_data(img + img_data, data_size, "ext-prop");
ut_asserteq(img_size, create_image(img, FIT_EXTERNAL, &info_write,
NULL));
- /*
- * Inflate data-size far beyond the image buffer and any plausible
- * load region. Without a bounds check, load_simple_fit() reads this
- * many bytes off the "device" before the hash is checked.
- */
node = fdt_path_offset(img, FIT_IMAGES_PATH);
ut_assert(node >= 0);
node = fdt_first_subnode(img, node);
ut_assert(node >= 0);
- ut_assertok(fdt_setprop_inplace_u32(img, node, FIT_DATA_SIZE_PROP,
- 0x40000000));
+ ut_assertok(fdt_setprop_inplace_u32(img, node, prop, value));
- spl_load_init(&load, spl_test_read, img, 1);
- ut_asserteq(-EFBIG, spl_load_simple_fit(&info_read, &load, 0, img));
+ spl_load_init(&load, h_read, img, bl_len);
+ ut_asserteq(expected,
+ spl_load_simple_fit(&info_read, &load, fit_offset, img));
free(img);
return 0;
}
+
+/*
+ * A FIT image's data-size property is not covered by the configuration
+ * signature, so it is untrusted input. load_simple_fit() must reject a
+ * data-size larger than the destination rather than overrun it, because the
+ * device read happens before the image hash is verified.
+ */
+static int spl_test_fit_external_oversize(struct unit_test_state *uts)
+{
+ if (!image_supported(FIT_EXTERNAL))
+ return -EAGAIN;
+
+ return check_fit_ext_prop(uts, FIT_DATA_SIZE_PROP, 0x40000000, 1,
+ spl_test_read, 0, -EFBIG);
+}
SPL_TEST(spl_test_fit_external_oversize, 0);
/*
+ * A data-offset which wraps past UINT32_MAX once the external-data base
+ * offset is added must be rejected before it is used as a read offset.
+ */
+static int spl_test_fit_data_offset_overflow(struct unit_test_state *uts)
+{
+ if (!image_supported(FIT_EXTERNAL))
+ return -EAGAIN;
+
+ return check_fit_ext_prop(uts, FIT_DATA_OFFSET_PROP, 0xffffffff, 1,
+ spl_test_read, 0, -EINVAL);
+}
+SPL_TEST(spl_test_fit_data_offset_overflow, 0);
+
+/*
+ * A data-size whose block-aligned read size wraps past ULONG_MAX must be
+ * rejected. Since data-size is a 32-bit property, the wrap is only reachable
+ * when ulong is 32 bits wide, so skip the test on other targets.
+ */
+static int spl_test_fit_data_size_overflow(struct unit_test_state *uts)
+{
+ if (!image_supported(FIT_EXTERNAL) || sizeof(ulong) != 4)
+ return -EAGAIN;
+
+ return check_fit_ext_prop(uts, FIT_DATA_SIZE_PROP, 0xffffffff, 2,
+ spl_test_read, 0, -EOVERFLOW);
+}
+SPL_TEST(spl_test_fit_data_size_overflow, 0);
+
+/* Device offset the reader pretends the FIT was loaded from */
+static ulong spl_test_fit_offset;
+
+static ulong spl_test_read_fit_offset(struct spl_load_info *load, ulong sector,
+ ulong count, void *buf)
+{
+ return spl_test_read(load, sector - spl_test_fit_offset, count, buf);
+}
+
+/*
+ * An aligned external-data offset which wraps past ULONG_MAX once the FIT's
+ * offset on the device is added must be rejected before it is used as a read
+ * offset.
+ */
+static int spl_test_fit_read_offset_overflow(struct unit_test_state *uts)
+{
+ if (!image_supported(FIT_EXTERNAL))
+ return -EAGAIN;
+
+ /* So that ULONG_MAX - fit_offset < the aligned external-data offset */
+ spl_test_fit_offset = ULONG_MAX - 0xfff;
+
+ return check_fit_ext_prop(uts, FIT_DATA_OFFSET_PROP, 0x1000, 1,
+ spl_test_read_fit_offset, spl_test_fit_offset,
+ -EINVAL);
+}
+SPL_TEST(spl_test_fit_read_offset_overflow, 0);
+
+/*
+ * A read whose end position (read_offset + size) wraps past the addressable
+ * range must be rejected.
+ */
+static int spl_test_fit_read_end_overflow(struct unit_test_state *uts)
+{
+ if (!image_supported(FIT_EXTERNAL))
+ return -EAGAIN;
+
+ /*
+ * The aligned external-data offset (0x2000 plus the size of the FIT
+ * itself) stays below the 0x2fff bytes remaining before ULONG_MAX, so
+ * read_offset passes the offset-wrap check above, but reading the
+ * SPL_TEST_DATA_SIZE bytes of data crosses past UINTPTR_MAX.
+ */
+ spl_test_fit_offset = ULONG_MAX - 0x2fff;
+
+ return check_fit_ext_prop(uts, FIT_DATA_OFFSET_PROP, 0x2000, 1,
+ spl_test_read_fit_offset, spl_test_fit_offset,
+ -EINVAL);
+}
+SPL_TEST(spl_test_fit_read_end_overflow, 0);
+
+/*
* LZMA is too complex to generate on the fly, so let's use some data I put in
* the oven^H^H^H^H compressed earlier
*/
diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
index 4b6707caf70..fd91b3f10a2 100644
--- a/test/py/tests/test_vboot.py
+++ b/test/py/tests/test_vboot.py
@@ -700,12 +700,12 @@ def test_vboot_ext_data_bounds(ubman):
fd.write(500 * b'\0')
testcases = [
- ('negative data-position',
- {'data-position': 0xffffffff}, 'Invalid external data position'),
- ('negative data-offset',
+ ('invalid data-position',
+ {'data-position': 0xffffffff}, 'FIT external data is out of bounds'),
+ ('invalid data-offset',
{'data-offset': 0xffffffff}, 'Invalid external data offset'),
- ('negative data-size',
- {'data-size': 0xffffffff}, 'Invalid external data size'),
+ ('invalid data-size',
+ {'data-size': 0xffffffff}, 'FIT external data is out of bounds'),
('off-bounds data-position',
{'data-position': 0x7fffffff}, 'FIT external data is out of bounds'),
('off-bounds data-offset',