From: Masami Hiramatsu (Google) <[email protected]>

Sashiko reported that in get_boot_config_from_initrd(), a crafted initrd
with a huge bootconfig size (such as 0xFFFFFFFF) can cause the pointer
arithmetic:

    data = ((void *)hdr) - size;

to wrap around on 32-bit systems (or when pointer subtraction overflows).
Because data wraps around, the subsequent bounds check:

    if ((unsigned long)data < initrd_start)

evaluates to false, bypassing the check. The kernel then calls
xbc_calc_checksum(data, size), which attempts to read 4GB of memory,
hitting unmapped pages and triggering a fatal kernel page fault during
early boot. Furthermore, on 64-bit systems with an initrd > 4.29 GB, an
unbounded 32-bit size can similarly bypass the initrd_start check.

Fix this by:
1. Ensuring the initrd is at least large enough to contain the bootconfig
   footer and verifying hdr is within the initrd bounds.
2. Checking that size does not exceed XBC_DATA_MAX and does not exceed
   the available space between initrd_start and hdr before performing
   pointer subtraction.

Fixes: de462e5f1071 ("bootconfig: Fix to remove bootconfig data from initrd 
while boot")
Cc: [email protected]
Reported-by: Sashiko <[email protected]>
Closes: https://lore.kernel.org/all/[email protected]/
Assisted-by: Antigravity:gemini-3.8-flash
Signed-off-by: Masami Hiramatsu (Google) <[email protected]>
---
 init/main.c |   14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/init/main.c b/init/main.c
index 2613d3f9b3ce..60c27d5f2bce 100644
--- a/init/main.c
+++ b/init/main.c
@@ -277,7 +277,8 @@ static void * __init get_boot_config_from_initrd(size_t 
*_size)
        u8 *hdr;
        int i;
 
-       if (!initrd_end)
+       if (!initrd_end || initrd_end < initrd_start ||
+           initrd_end - initrd_start < BOOTCONFIG_MAGIC_LEN + 8)
                return NULL;
 
        data = (char *)initrd_end - BOOTCONFIG_MAGIC_LEN;
@@ -294,16 +295,21 @@ static void * __init get_boot_config_from_initrd(size_t 
*_size)
 
 found:
        hdr = (u8 *)(data - 8);
+       if ((unsigned long)hdr < initrd_start)
+               return NULL;
+
        size = get_unaligned_le32(hdr);
        csum = get_unaligned_le32(hdr + 4);
 
-       data = ((void *)hdr) - size;
-       if ((unsigned long)data < initrd_start) {
-               pr_err("bootconfig size %d is greater than initrd size %ld\n",
+       if (size > XBC_DATA_MAX ||
+           size > ((unsigned long)hdr - initrd_start)) {
+               pr_err("bootconfig size %u is greater than initrd size %lu\n",
                        size, initrd_end - initrd_start);
                return NULL;
        }
 
+       data = ((void *)hdr) - size;
+
        if (xbc_calc_checksum(data, size) != csum) {
                pr_err("bootconfig checksum failed\n");
                return NULL;


Reply via email to