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]>
---
 Changes in v4:
  - Accurate the error message for size > XBC_DATA_MAX in
    get_boot_config_from_initrd().
  - Remove redundant (and wrong) size >= XBC_DATA_MAX check from
    setup_boot_config().
---
 init/main.c |   25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/init/main.c b/init/main.c
index 2613d3f9b3ce..16749bb7a219 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,26 @@ 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) {
+               pr_err("bootconfig size %u is greater than max size %d\n",
+                       size, XBC_DATA_MAX);
+               return NULL;
+       }
+
+       if (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;
@@ -394,12 +405,6 @@ static void __init setup_boot_config(void)
                return;
        }
 
-       if (size >= XBC_DATA_MAX) {
-               pr_err("bootconfig size %ld greater than max size %d\n",
-                       (long)size, XBC_DATA_MAX);
-               return;
-       }
-
        ret = xbc_init(data, size, &msg, &pos);
        if (ret < 0) {
                if (pos < 0)


Reply via email to