drivers/fpga/microchip-spi.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
From: Sebastian Josue Alba Vives <sebasjosue84@gmail.com>
mpf_ops_parse_header() reads several fields from the bitstream file
and uses them as offsets and sizes without validating them against the
buffer size, leading to multiple out-of-bounds read vulnerabilities:
1. There is no check that count is large enough to read header_size
at MPF_HEADER_SIZE_OFFSET (24). Add a minimum count check.
2. When header_size (u8 from file) is 0, the expression
*(buf + header_size - 1) reads one byte before the buffer.
Return -EINVAL since retrying with a larger buffer cannot fix
a zero header_size.
3. In the block lookup loop, block_id_offset and block_start_offset
advance by MPF_LOOKUP_TABLE_RECORD_SIZE (9) each iteration with
blocks_num (u8) controlling the count. With a small buffer, these
offsets exceed count, causing OOB reads via get_unaligned_le32().
Return -EAGAIN with updated header_size so the retry mechanism
can request a larger buffer.
4. components_num is read from MPF_DATA_SIZE_OFFSET without checking
that the offset is within bounds. Add a bounds check.
5. components_size_start (from file) and component_size_byte_num
(derived from components_num, u16 from file) are used as offsets
into buf without validation. On 32-bit architectures, their sum
could overflow, bypassing the bounds check. Add an overflow check
before the bounds comparison.
Add bounds checks for all five cases.
Fixes: 5f8d4a9008307 ("fpga: microchip-spi: add Microchip MPF FPGA manager")
Cc: stable@vger.kernel.org
Signed-off-by: Sebastian Alba Vives <sebasjosue84@gmail.com>
---
Changes in v3:
- Add overflow check for 32-bit architectures in component size loop
- Add bounds check for MPF_DATA_SIZE_OFFSET read
- Update info->header_size before returning -EAGAIN in block loop
so the retry mechanism can request a larger buffer
Changes in v2:
- Return -EINVAL instead of -EAGAIN for header_size == 0
- Return -EAGAIN instead of -EINVAL in block lookup loop
- Add count check before reading at MPF_HEADER_SIZE_OFFSET
drivers/fpga/microchip-spi.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/fpga/microchip-spi.c b/drivers/fpga/microchip-spi.c
index 6134cea..10be986 100644
--- a/drivers/fpga/microchip-spi.c
+++ b/drivers/fpga/microchip-spi.c
@@ -115,7 +115,13 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
return -EINVAL;
}
+ if (count < MPF_HEADER_SIZE_OFFSET + 1)
+ return -EINVAL;
+
header_size = *(buf + MPF_HEADER_SIZE_OFFSET);
+ if (!header_size)
+ return -EINVAL;
+
if (header_size > count) {
info->header_size = header_size;
return -EAGAIN;
@@ -139,6 +145,12 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
bitstream_start = 0;
while (blocks_num--) {
+ if (block_id_offset >= count ||
+ block_start_offset + sizeof(u32) > count) {
+ info->header_size = block_start_offset + sizeof(u32);
+ return -EAGAIN;
+ }
+
block_id = *(buf + block_id_offset);
block_start = get_unaligned_le32(buf + block_start_offset);
@@ -175,6 +187,9 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
* to each other. Image header should be extended by now up to where
* actual bitstream starts, so no need for overflow check anymore.
*/
+ if (MPF_DATA_SIZE_OFFSET + sizeof(u16) > count)
+ return -EINVAL;
+
components_num = get_unaligned_le16(buf + MPF_DATA_SIZE_OFFSET);
for (i = 0; i < components_num; i++) {
@@ -183,6 +198,11 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
component_size_byte_off =
(i * MPF_BITS_PER_COMPONENT_SIZE) % BITS_PER_BYTE;
+ if (components_size_start + component_size_byte_num < components_size_start ||
+ components_size_start + component_size_byte_num +
+ sizeof(u32) > count)
+ return -EINVAL;
+
component_size = get_unaligned_le32(buf +
components_size_start +
component_size_byte_num);
--
2.43.0
> @@ -139,6 +145,12 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
> bitstream_start = 0;
>
> while (blocks_num--) {
> + if (block_id_offset >= count ||
> + block_start_offset + sizeof(u32) > count) {
> + info->header_size = block_start_offset + sizeof(u32);
> + return -EAGAIN;
> + }
> +
The image header has already been extended up to all look-up table for
blocks, is it?
header_size += blocks_num * MPF_LOOKUP_TABLE_RECORD_SIZE;
if (header_size > count) {
info->header_size = header_size;
return -EAGAIN;
}
> block_id = *(buf + block_id_offset);
> block_start = get_unaligned_le32(buf + block_start_offset);
>
> @@ -175,6 +187,9 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
> * to each other. Image header should be extended by now up to where
> * actual bitstream starts, so no need for overflow check anymore.
> */
> + if (MPF_DATA_SIZE_OFFSET + sizeof(u16) > count)
> + return -EINVAL;
> +
Do you notice the comments above? IIUC it says all these header info
should be before actual bitstream starts, if we could ensure this, we
don't need to check the addresses inside the header byte by byte.
I think it is important we understand the structure of the image file
first then meaningfully check the boundaries chunk by chunk, rather than
byte by byte, which makes code unreadable.
Thanks,
Yilun
> components_num = get_unaligned_le16(buf + MPF_DATA_SIZE_OFFSET);
>
> for (i = 0; i < components_num; i++) {
> @@ -183,6 +198,11 @@ static int mpf_ops_parse_header(struct fpga_manager *mgr,
> component_size_byte_off =
> (i * MPF_BITS_PER_COMPONENT_SIZE) % BITS_PER_BYTE;
>
> + if (components_size_start + component_size_byte_num < components_size_start ||
> + components_size_start + component_size_byte_num +
> + sizeof(u32) > count)
> + return -EINVAL;
> +
> component_size = get_unaligned_le32(buf +
> components_size_start +
> component_size_byte_num);
> --
> 2.43.0
>
© 2016 - 2026 Red Hat, Inc.