F-4645: bound load_linux kernel size to prevent syssize*16 overflow

load_linux() computed the protected-mode kernel size as the uint32_t
product param.hdr.syssize * 16 (src/x86/linux_loader.c), where syssize
is copied verbatim from the (authenticated) bzImage at offset 0x1f4.
The multiplication wraps for any syssize > 0x0FFFFFFF: syssize=0x10000000
yields kernel_size=0 (DoS), and syssize=0x1FFFFFFF/0xFFFFFFFF yields
kernel_size=0xFFFFFFF0 (~4 GiB). That value fed straight into
memcpy((uint8_t*)KERNEL_LOAD_ADDRESS, linux_image + param_size,
kernel_size) with no cap, overwriting wolfBoot stage2, FSP data, and the
heap (CWE-190 -> CWE-680).

Fix at the root: linux_kernel_size() computes syssize * 16 in 64-bit and
rejects the image (panic) when the result is zero or does not fit in the
destination window [KERNEL_LOAD_ADDRESS, tolum). tolum is the top of low
usable memory the FSP already reports and that the ELF boot path uses as
its load upper bound (src/boot_x86_fsp_payload.c). The kernel load only
runs under WOLFBOOT_FSP (the non-FSP path panics earlier at the memory
map step), so tolum is always available there.

Add unit-linux-loader-syssize regression test (x86 32bit, standalone)
that feeds the PoC overflow values and asserts they are rejected while a
legitimate kernel and the exact-fit boundary are accepted.
pull/788/head
Daniele Lacamera 2026-06-05 19:33:51 +02:00
parent 2766450123
commit d175c819cd
3 changed files with 127 additions and 2 deletions

View File

@ -110,10 +110,27 @@ static int linux_boot_params_fill_memory_map(struct boot_params *bp,
#define KERNEL_LOAD_ADDRESS 0x100000
#define KERNEL_CMDLINE_ADDRESS 0x10000
/* Compute the protected-mode kernel size (syssize * 16) in 64-bit to avoid the
* uint32_t multiplication wrap, and reject any image whose kernel would not fit
* in the destination window [KERNEL_LOAD_ADDRESS, load_limit). Returns 0 on
* success, -1 if the size is zero or out of range. */
static int linux_kernel_size(uint32_t syssize, uint32_t load_limit,
uint32_t *kernel_size)
{
uint64_t ksz = (uint64_t)syssize * 16u;
if (load_limit <= KERNEL_LOAD_ADDRESS)
return -1;
if (ksz == 0 || ksz > (uint64_t)(load_limit - KERNEL_LOAD_ADDRESS))
return -1;
*kernel_size = (uint32_t)ksz;
return 0;
}
void load_linux(uint8_t *linux_image, void *params, const char *cmd_line)
{
struct boot_params param = { 0 };
uint32_t kernel_size, param_size;
uint32_t kernel_size, param_size, load_limit;
uint8_t *image_boot_param;
uint16_t end_of_header_off;
uint8_t *_cmd_line;
@ -145,7 +162,15 @@ void load_linux(uint8_t *linux_image, void *params, const char *cmd_line)
memcpy(_cmd_line, (uint8_t*)cmd_line, strlen(cmd_line)+1);
param.hdr.type_of_loader = 0xff;
param.hdr.cmd_line_ptr = (uint32_t)(uintptr_t)_cmd_line;
kernel_size = param.hdr.syssize * 16;
#ifdef WOLFBOOT_FSP
load_limit = ((struct stage2_parameter *)params)->tolum;
#else
load_limit = 0;
#endif /* WOLFBOOT_FSP */
if (linux_kernel_size(param.hdr.syssize, load_limit, &kernel_size) != 0) {
wolfBoot_printf("invalid kernel size" ENDLINE);
wolfBoot_panic();
}
memcpy((uint8_t *)KERNEL_LOAD_ADDRESS, linux_image + param_size,
kernel_size);

View File

@ -61,6 +61,7 @@ TESTS+=unit-tpm-check-rot-auth
TESTS+=unit-tpm-api-names
TESTS+=unit-fit-gzip unit-fit-nogzip
TESTS+=unit-linux-loader-e820
TESTS+=unit-linux-loader-syssize
include unit-sign-encrypted-output.mkfrag
@ -272,6 +273,11 @@ unit-linux-loader-e820: ../../include/target.h unit-linux-loader-e820.c
-g -DUNIT_TEST -DWOLFBOOT_FSP -DUCODE0_ADDRESS=0 \
-DWOLFBOOT_LOAD_BASE=0x100000
unit-linux-loader-syssize: ../../include/target.h unit-linux-loader-syssize.c
gcc -m32 -o $@ unit-linux-loader-syssize.c -I. -I../../src -I../../include \
-g -DUNIT_TEST -DWOLFBOOT_FSP -DUCODE0_ADDRESS=0 \
-DWOLFBOOT_LOAD_BASE=0x100000
unit-boot-x86-fsp: ../../include/target.h unit-boot-x86_fsp.c
gcc -o $@ $^ $(CFLAGS) -DWOLFBOOT_LOAD_BASE=0x100000 -DWOLFBOOT_FSP \
-DUCODE0_ADDRESS=0 -ffunction-sections -fdata-sections $(LDFLAGS) \

View File

@ -0,0 +1,94 @@
/* unit-linux-loader-syssize.c
*
* Regression test for F-4645: load_linux() computed the protected-mode kernel
* size as the uint32_t product param.hdr.syssize * 16, which wraps for any
* syssize > 0x0FFFFFFF. The wrapped value (0 or ~4 GiB) was passed straight to
* memcpy() into KERNEL_LOAD_ADDRESS with no bounds check.
*
* linux_kernel_size() now performs the multiplication in 64-bit and rejects a
* size that is zero or does not fit in the destination window
* [KERNEL_LOAD_ADDRESS, load_limit). This test exercises that helper directly.
*
* Built for x86 32bit (the only target supported by linux_loader.c), without
* the check framework, since 32bit libcheck is not generally available.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "x86/hob.h"
#include "x86/linux_loader.h"
#include "../../src/x86/hob.c"
#include "../../src/x86/linux_loader.c"
/* A generous low-memory window (256 MiB) used as the destination limit. */
#define LOAD_LIMIT 0x10000000u
int main(void)
{
uint32_t ksz;
int ret;
/* The PoC overflow values: all wrap when computed as uint32_t. */
const uint32_t wrap_cases[] = {
0x10000000u, /* 32bit product == 0x00000000 (DoS) */
0x1FFFFFFFu, /* 32bit product == 0xFFFFFFF0 (~4 GiB) */
0xFFFFFFFFu, /* 32bit product == 0xFFFFFFF0 (~4 GiB) */
};
unsigned i;
for (i = 0; i < sizeof(wrap_cases) / sizeof(wrap_cases[0]); i++) {
uint32_t syssize = wrap_cases[i];
/* Demonstrate the original wrap: the 32bit product no longer matches
* the true 64bit size, which is exactly why a bound is required. */
if ((uint32_t)(syssize * 16u) == (uint64_t)syssize * 16u) {
printf("FAIL: case 0x%08x does not actually wrap\n", syssize);
return 1;
}
ksz = 0xDEADBEEF;
ret = linux_kernel_size(syssize, LOAD_LIMIT, &ksz);
if (ret == 0) {
printf("FAIL: overflowing syssize 0x%08x accepted (ksz=0x%08x)\n",
syssize, ksz);
return 1;
}
}
/* A size that exceeds the window (but does not wrap) must be rejected. */
ret = linux_kernel_size(LOAD_LIMIT / 16u, LOAD_LIMIT, &ksz);
if (ret == 0) {
printf("FAIL: oversized kernel accepted\n");
return 1;
}
/* Zero-sized kernel must be rejected. */
if (linux_kernel_size(0, LOAD_LIMIT, &ksz) == 0) {
printf("FAIL: zero-sized kernel accepted\n");
return 1;
}
/* A legitimate kernel that fits must be accepted with the exact size. */
ksz = 0;
ret = linux_kernel_size(0x10000u, LOAD_LIMIT, &ksz);
if (ret != 0 || ksz != 0x10000u * 16u) {
printf("FAIL: valid kernel rejected (ret=%d ksz=0x%08x)\n", ret, ksz);
return 1;
}
/* The largest kernel that exactly fills the window must be accepted. */
ret = linux_kernel_size((LOAD_LIMIT - KERNEL_LOAD_ADDRESS) / 16u,
LOAD_LIMIT, &ksz);
if (ret != 0 || ksz != (LOAD_LIMIT - KERNEL_LOAD_ADDRESS)) {
printf("FAIL: exact-fit kernel rejected (ret=%d ksz=0x%08x)\n",
ret, ksz);
return 1;
}
printf("PASS\n");
return 0;
}