mirror of https://github.com/wolfSSL/wolfBoot.git
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
parent
2766450123
commit
d175c819cd
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue