public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] initramfs: Add size validation to prevent tmpfs exhaustion
@ 2025-03-14  5:04 Stephen Eta Zhou
  2025-03-17  7:21 ` David Disseldorp
  0 siblings, 1 reply; 12+ messages in thread
From: Stephen Eta Zhou @ 2025-03-14  5:04 UTC (permalink / raw)
  To: jsperbeck@google.com, akpm@linux-foundation.org, ddiss@suse.de
  Cc: gregkh@linuxfoundation.org, lukas@wunner.de,
	wufan@linux.microsoft.com, linux-kernel@vger.kernel.org

From 3499daeb5caf934f08a485027b5411f9ef82d6be Mon Sep 17 00:00:00 2001
From: Stephen Eta Zhou <stephen.eta.zhou@outlook.com>
Date: Fri, 14 Mar 2025 12:32:59 +0800
Subject: [PATCH] initramfs: Add size validation to prevent tmpfs exhaustion

When initramfs is loaded into a small memory environment, if its size
exceeds the tmpfs max blocks limit, the loading will fail. Additionally,
if the required blocks are close to the tmpfs max blocks boundary,
subsequent drivers or subsystems using tmpfs may fail to initialize.

To prevent this, the size limit is set to half of tmpfs max blocks.
This ensures that initramfs can complete its mission without exhausting
tmpfs resources, as user-space programs may also rely on tmpfs after boot.

This patch adds a validation mechanism to check the decompressed size
of initramfs based on its compression type and ratio. If the required
blocks exceed half of the tmpfs max blocks limit, the loading will be
aborted with an appropriate error message, exposing the issue early
and preventing further escalation.

Signed-off-by: Stephen Eta Zhou <stephen.eta.zhou@outlook.com>
---
 init/initramfs.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 162 insertions(+)

diff --git a/init/initramfs.c b/init/initramfs.c
index b2f7583bb1f5..dadda0a42b48 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -497,6 +497,157 @@ static unsigned long my_inptr __initdata; /* index of next byte to be processed
 
 #include <linux/decompress/generic.h>
 
+#ifdef CONFIG_TMPFS
+/*
+ * struct compress_info - Describes a compression method.
+ *
+ * @magic: Magic numbers to identify the compression method (e.g., GZIP, XZ, etc.).
+ *         Each magic number is a byte array of maximum length 256.
+ *         The first dimension (2) represents the number of possible magic numbers.
+ * @rate: Compression ratio, calculated as R = (compressed size / original size) * 100.
+ *        The value is in percentage (0-100).
+ * @mark: Name of the compression scheme (e.g., "GZIP", "XZ").
+ * @len: Length of each magic byte array. Used for comparison with memcmp.
+ *       The first dimension (2) corresponds to the number of magic numbers.
+ * @magic_max: Maximum number of magic numbers supported (used when multiple magics are possible).
+ */
+struct compress_info {
+     unsigned char magic[2][256];
+     unsigned long rate;
+     char *mark;
+     size_t len[2];
+     size_t magic_max;
+};
+
+static struct compress_info cfm[] __initdata = {
+     {
+           .mark = "Gzip",
+           .magic = { { 0x1F, 0x8B } },
+           .len = { 2 },
+           .rate = 43,
+           .magic_max = 1,
+     },
+     {
+           .mark = "Bzip2",
+           .magic = { { 0x42, 0x5A, 0x68 } },
+           .len = { 3 },
+           .rate = 22,
+           .magic_max = 1,
+     },
+     {
+           .mark = "LZMA",
+           .magic = { { 0x5D, 0x00, 0x00 }, { 0xFF, 0x5D, 0x00 } },
+           .len = { 3, 3 },
+           .rate = 5,
+           .magic_max = 2,
+     },
+     {
+           .mark = "XZ",
+           .magic = { { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00 } },
+           .len = { 6 },
+           .rate = 7,
+           .magic_max = 1,
+     },
+     {
+           .mark = "LZO",
+           .magic = { { 0x89, 0x4C, 0x5A, 0x4F, 0x00, 0x0D, 0x0A, 0x1A, 0x0A } },
+           .len = { 9 },
+           .rate = 47,
+           .magic_max = 1,
+     },
+     {
+           .mark = "LZ4",
+           .magic = {
+                             { 0x04, 0x22, 0x4D, 0x18 },
+                             { 0x02, 0x21, 0x4C, 0x18 }
+                        },
+           .len = { 4 },
+           .rate = 52,
+           .magic_max = 2,
+     },
+     {
+           .mark = "ZSTD",
+           .magic = { { 0x28, 0xB5, 0x2F, 0xFD } },
+           .len = { 4 },
+           .rate = 7,
+           .magic_max = 1,
+     },
+     {
+           .mark = "None",
+           .magic = {
+                             { 0x30, 0x37, 0x30, 0x37, 0x30, 0x31 },
+                             { 0x30, 0x37, 0x30, 0x37, 0x30, 0x32 }
+                        },
+           .len = { 6, 6 },
+           .rate = 0,
+           .magic_max = 2,
+     },
+};
+
+static int __init validate_rootfs_size(char *buf, unsigned long len)
+{
+     unsigned long i, j, result, quotient, half_tmpfs_blocks;
+
+     /*
+      * Calculate how many blocks are needed to decompress
+      * and check if they are within a reasonable range.
+      */
+     for (i = 0; i < ARRAY_SIZE(cfm); ++i) {
+           for (j = 0; j < cfm[i].magic_max; ++j) {
+                 if (memcmp(buf, cfm[i].magic[j], cfm[i].len[j]) == 0) {
+                       pr_debug("Compression method: %\n", cfm[i].mark);
+                       /*
+                        * The calculation is divided into three steps:
+                        * 1. Calculate the decompressed size based on the ratio.
+                        * 2. Check for potential overflow risks and ensure that
+                        *    the temporary decompressed
+                        *    initramfs does not exceed the maximum range of 2^(32/64),
+                        *    ensuring that the initramfs size does not approach the
+                        *    memory addressing limit (this cannot be fully guaranteed).
+                        * 3. Determine whether the required page size exceeds 1/4 of
+                        *    the total memory pages, restricting it from using excessively
+                        *    large amounts of memory pages.
+                        *
+                        * Note1: Here, `len` cannot be directly multiplied by 100,
+                        *        as it may cause overflow.
+                        *        Dividing by `rate` first and then multiplying by 100
+                        *        can effectively reduce the risk of overflow.
+                        *
+                        * Note2: Due to integer division and rounding,
+                        *        the calculated size may deviate by a few MB.
+                        */
+                       quotient = len / cfm[i].rate;
+
+                       if (quotient > ULONG_MAX / 100)
+                             goto err_overflow;
+                       else
+                             result = (quotient * 100) / PAGE_SIZE;
+
+                       /*
+                        * totalram_pages() / 2 = tmpfs max blocks
+                        */
+                       half_tmpfs_blocks = (totalram_pages() / 2) / 2;
+                       if (result > half_tmpfs_blocks)
+                             goto err_nomem;
+
+                       return 0;
+                 }
+           }
+     }
+
+     pr_err("This compression format is not supported.\n");
+     return -EOPNOTSUPP;
+
+err_overflow:
+     pr_err("Decompressed size overflow!\n");
+     return -ERANGE;
+err_nomem:
+     pr_err("Decompressed size exceeds tmpfs max blocks limit!\n");
+     return -ENOMEM;
+
+}
+#endif
+
 static char * __init unpack_to_rootfs(char *buf, unsigned long len)
 {
      long written;
@@ -504,6 +655,17 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
      const char *compress_name;
      static __initdata char msg_buf[64];
 
+#ifdef CONFIG_TMPFS
+     int ret = validate_rootfs_size(buf, len);
+
+     if (ret) {
+           snprintf(msg_buf, sizeof(msg_buf),
+                       "Rootfs does not comply with the rules, error code: %d", ret);
+           message = msg_buf;
+           return message;
+     }
+#endif
+
      header_buf = kmalloc(110, GFP_KERNEL);
      symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
      name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 12+ messages in thread
* Re: [RFC PATCH] initramfs: Add size validation to prevent tmpfs exhaustion
@ 2025-03-19  7:59 Stephen Eta Zhou
  0 siblings, 0 replies; 12+ messages in thread
From: Stephen Eta Zhou @ 2025-03-19  7:59 UTC (permalink / raw)
  To: Krzysztof Kozlowski, David Disseldorp
  Cc: jsperbeck@google.com, akpm@linux-foundation.org,
	gregkh@linuxfoundation.org, lukas@wunner.de,
	wufan@linux.microsoft.com, linux-kernel@vger.kernel.org,
	linux-fsdevel@vger.kernel.org

On Wednesday, March 19, 2025 03:01, Krzysztof Kozlowski  wrote:
> What is this header doing this? Use standard mailing list response
> style, not some copy-paste and then quote entire irrelevant context.
oh....sorry, My email client carries these contents...
I will be more careful in the future. Apologies for the disruption to the
email thread.

Thanks,
Stephen

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2025-03-19  7:59 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-14  5:04 [RFC PATCH] initramfs: Add size validation to prevent tmpfs exhaustion Stephen Eta Zhou
2025-03-17  7:21 ` David Disseldorp
2025-03-17  9:41   ` Stephen Eta Zhou
2025-03-18  1:14     ` David Disseldorp
2025-03-18  4:47       ` Stephen Eta Zhou
2025-03-18  6:28       ` Stephen Eta Zhou
2025-03-18  9:51         ` David Disseldorp
2025-03-18 12:36           ` Stephen Eta Zhou
2025-03-18 11:55   ` Krzysztof Kozlowski
2025-03-18 12:46     ` Stephen Eta Zhou
2025-03-18 19:01       ` Krzysztof Kozlowski
  -- strict thread matches above, loose matches on Subject: below --
2025-03-19  7:59 Stephen Eta Zhou

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox