[PATCH v2 0/9] initramfs: kunit tests and cleanups

2024-11-06 Thread David Disseldorp
This patchset adds basic kunit test coverage for initramfs unpacking
and cleans up some minor buffer handling issues / inefficiencies.

Changes since v2 (patch 2 only):
- fix !CONFIG_INITRAMFS_PRESERVE_MTIME kunit test checks
- add test MODULE_DESCRIPTION(), as suggested by Jeff Johnson
- add some missing headers, reported by kernel test robot

Changes since v1 (RFC):
- rebase atop v6.12-rc6 and filename field overrun fix from
  https://lore.kernel.org/r/20241030035509.20194-2-dd...@suse.de
- add unit test coverage (new patches 1 and 2)
- add patch: fix hardlink hash leak without TRAILER
- rework patch: avoid static buffer for error message
  + drop unnecessary message propagation
- drop patch: cpio_buf reuse for built-in and bootloader initramfs
  + no good justification for the change

Feedback appreciated.

David Disseldorp (9):
  init: add initramfs_internal.h
  initramfs_test: kunit tests for initramfs unpacking
  vsprintf: add simple_strntoul
  initramfs: avoid memcpy for hex header fields
  initramfs: remove extra symlink path buffer
  initramfs: merge header_buf and name_buf
  initramfs: reuse name_len for dir mtime tracking
  initramfs: fix hardlink hash leak without TRAILER
  initramfs: avoid static buffer for error message

 include/linux/kstrtox.h   |   1 +
 init/.kunitconfig |   3 +
 init/Kconfig  |   7 +
 init/Makefile |   1 +
 init/initramfs.c  |  73 +
 init/initramfs_internal.h |   8 +
 init/initramfs_test.c | 403 ++
 lib/vsprintf.c|   7 +
 8 files changed, 471 insertions(+), 32 deletions(-)
 create mode 100644 init/.kunitconfig
 create mode 100644 init/initramfs_internal.h
 create mode 100644 init/initramfs_test.c



[PATCH v3 1/9] init: add initramfs_internal.h

2024-11-06 Thread David Disseldorp
The new header only exports a single unpack function and a CPIO_HDRLEN
constant for future test use.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c  | 16 +---
 init/initramfs_internal.h |  8 
 2 files changed, 21 insertions(+), 3 deletions(-)
 create mode 100644 init/initramfs_internal.h

diff --git a/init/initramfs.c b/init/initramfs.c
index b2f7583bb1f5c..002e83ae12ac7 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -20,6 +20,7 @@
 #include 
 
 #include "do_mounts.h"
+#include "initramfs_internal.h"
 
 static __initdata bool csum_present;
 static __initdata u32 io_csum;
@@ -256,7 +257,7 @@ static __initdata char *header_buf, *symlink_buf, *name_buf;
 
 static int __init do_start(void)
 {
-   read_into(header_buf, 110, GotHeader);
+   read_into(header_buf, CPIO_HDRLEN, GotHeader);
return 0;
 }
 
@@ -497,14 +498,23 @@ static unsigned long my_inptr __initdata; /* index of 
next byte to be processed
 
 #include 
 
-static char * __init unpack_to_rootfs(char *buf, unsigned long len)
+/**
+ * unpack_to_rootfs - decompress and extract an initramfs archive
+ * @buf: input initramfs archive to extract
+ * @len: length of initramfs data to process
+ *
+ * Returns: NULL for success or an error message string
+ *
+ * This symbol shouldn't be used externally. It's available for unit tests.
+ */
+char * __init unpack_to_rootfs(char *buf, unsigned long len)
 {
long written;
decompress_fn decompress;
const char *compress_name;
static __initdata char msg_buf[64];
 
-   header_buf = kmalloc(110, GFP_KERNEL);
+   header_buf = kmalloc(CPIO_HDRLEN, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
 
diff --git a/init/initramfs_internal.h b/init/initramfs_internal.h
new file mode 100644
index 0..233dad16b0a00
--- /dev/null
+++ b/init/initramfs_internal.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef __INITRAMFS_INTERNAL_H__
+#define __INITRAMFS_INTERNAL_H__
+
+char *unpack_to_rootfs(char *buf, unsigned long len);
+#define CPIO_HDRLEN 110
+
+#endif
-- 
2.43.0




[PATCH v3 2/9] initramfs_test: kunit tests for initramfs unpacking

2024-11-06 Thread David Disseldorp
Provide some basic initramfs unpack sanity tests covering:
- simple file / dir extraction
- filename field overrun, as reported and fixed separately via
  https://lore.kernel.org/r/20241030035509.20194-2-dd...@suse.de
- "070702" cpio data checksums
- hardlinks

Signed-off-by: David Disseldorp 
---
 init/.kunitconfig |   3 +
 init/Kconfig  |   7 +
 init/Makefile |   1 +
 init/initramfs_test.c | 408 ++
 4 files changed, 419 insertions(+)
 create mode 100644 init/.kunitconfig
 create mode 100644 init/initramfs_test.c

diff --git a/init/.kunitconfig b/init/.kunitconfig
new file mode 100644
index 0..acb906b1a5f98
--- /dev/null
+++ b/init/.kunitconfig
@@ -0,0 +1,3 @@
+CONFIG_KUNIT=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_TEST=y
diff --git a/init/Kconfig b/init/Kconfig
index c521e1421ad4a..cf8327cdd6739 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1431,6 +1431,13 @@ config INITRAMFS_PRESERVE_MTIME
 
  If unsure, say Y.
 
+config INITRAMFS_TEST
+   bool "Test initramfs cpio archive extraction" if !KUNIT_ALL_TESTS
+   depends on BLK_DEV_INITRD && KUNIT=y
+   default KUNIT_ALL_TESTS
+   help
+ Build KUnit tests for initramfs. See Documentation/dev-tools/kunit
+
 choice
prompt "Compiler optimization level"
default CC_OPTIMIZE_FOR_PERFORMANCE
diff --git a/init/Makefile b/init/Makefile
index 10b652d33e872..d6f75d8907e09 100644
--- a/init/Makefile
+++ b/init/Makefile
@@ -12,6 +12,7 @@ else
 obj-$(CONFIG_BLK_DEV_INITRD)   += initramfs.o
 endif
 obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
+obj-$(CONFIG_INITRAMFS_TEST)   += initramfs_test.o
 
 obj-y  += init_task.o
 
diff --git a/init/initramfs_test.c b/init/initramfs_test.c
new file mode 100644
index 0..84b21f465bc3d
--- /dev/null
+++ b/init/initramfs_test.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "initramfs_internal.h"
+
+struct initramfs_test_cpio {
+   char *magic;
+   unsigned int ino;
+   unsigned int mode;
+   unsigned int uid;
+   unsigned int gid;
+   unsigned int nlink;
+   unsigned int mtime;
+   unsigned int filesize;
+   unsigned int devmajor;
+   unsigned int devminor;
+   unsigned int rdevmajor;
+   unsigned int rdevminor;
+   unsigned int namesize;
+   unsigned int csum;
+   char *fname;
+   char *data;
+};
+
+static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out)
+{
+   int i;
+   size_t off = 0;
+
+   for (i = 0; i < csz; i++) {
+   char *pos = &out[off];
+   struct initramfs_test_cpio *c = &cs[i];
+   size_t thislen;
+
+   /* +1 to account for nulterm */
+   thislen = sprintf(pos, "%s"
+   "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x"
+   "%s",
+   c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink,
+   c->mtime, c->filesize, c->devmajor, c->devminor,
+   c->rdevmajor, c->rdevminor, c->namesize, c->csum,
+   c->fname) + 1;
+   pr_debug("packing (%zu): %.*s\n", thislen, (int)thislen, pos);
+   off += thislen;
+   while (off & 3)
+   out[off++] = '\0';
+
+   memcpy(&out[off], c->data, c->filesize);
+   off += c->filesize;
+   while (off & 3)
+   out[off++] = '\0';
+   }
+
+   return off;
+}
+
+static void __init initramfs_test_extract(struct kunit *test)
+{
+   char *err, *cpio_srcbuf;
+   size_t len;
+   struct timespec64 ts_before, ts_after;
+   struct kstat st = {};
+   struct initramfs_test_cpio c[] = { {
+   .magic = "070701",
+   .ino = 1,
+   .mode = S_IFREG | 0777,
+   .uid = 12,
+   .gid = 34,
+   .nlink = 1,
+   .mtime = 56,
+   .filesize = 0,
+   .devmajor = 0,
+   .devminor = 1,
+   .rdevmajor = 0,
+   .rdevminor = 0,
+   .namesize = sizeof("initramfs_test_extract"),
+   .csum = 0,
+   .fname = "initramfs_test_extract",
+   }, {
+   .magic = "070701",
+   .ino = 2,
+   .mode = S_IFDIR | 0777,
+   .nlink = 1,
+   .mtime = 57,
+   .devminor = 1,
+   .namesize = sizeof("initramfs_test_extract_dir"),
+   .fname = "initramfs_test_extract_di

[PATCH v3 3/9] vsprintf: add simple_strntoul

2024-11-06 Thread David Disseldorp
cpio extraction currently does a memcpy to ensure that the archive hex
fields are null terminated for simple_strtoul(). simple_strntoul() will
allow us to avoid the memcpy.

Signed-off-by: David Disseldorp 
---
 include/linux/kstrtox.h | 1 +
 lib/vsprintf.c  | 7 +++
 2 files changed, 8 insertions(+)

diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index 7fcf29a4e0de4..6ea897222af1d 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -143,6 +143,7 @@ static inline int __must_check kstrtos32_from_user(const 
char __user *s, size_t
  */
 
 extern unsigned long simple_strtoul(const char *,char **,unsigned int);
+extern unsigned long simple_strntoul(const char *,char **,unsigned int,size_t);
 extern long simple_strtol(const char *,char **,unsigned int);
 extern unsigned long long simple_strtoull(const char *,char **,unsigned int);
 extern long long simple_strtoll(const char *,char **,unsigned int);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index c5e2ec9303c5d..32eacaae97990 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -114,6 +114,13 @@ unsigned long simple_strtoul(const char *cp, char **endp, 
unsigned int base)
 }
 EXPORT_SYMBOL(simple_strtoul);
 
+unsigned long simple_strntoul(const char *cp, char **endp, unsigned int base,
+ size_t max_chars)
+{
+   return simple_strntoull(cp, endp, base, max_chars);
+}
+EXPORT_SYMBOL(simple_strntoul);
+
 /**
  * simple_strtol - convert a string to a signed long
  * @cp: The start of the string
-- 
2.43.0




[PATCH v3 6/9] initramfs: merge header_buf and name_buf

2024-11-06 Thread David Disseldorp
header_buf is only used in FSM states up to GotHeader, while name_buf is
only used in states following GotHeader (Collect is shared, but the
collect pointer tracks each buffer). These buffers can therefore be
combined into a single cpio_buf, which can be used for both header and
file name storage.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 26 +++---
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 59b4a43fa491b..4e2506a4bc76f 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -250,11 +250,11 @@ static void __init read_into(char *buf, unsigned size, 
enum state next)
}
 }
 
-static __initdata char *header_buf, *name_buf;
+static __initdata char *cpio_buf;
 
 static int __init do_start(void)
 {
-   read_into(header_buf, CPIO_HDRLEN, GotHeader);
+   read_into(cpio_buf, CPIO_HDRLEN, GotHeader);
return 0;
 }
 
@@ -294,14 +294,14 @@ static int __init do_header(void)
if (S_ISLNK(mode)) {
if (body_len > PATH_MAX)
return 0;
-   collect = collected = name_buf;
+   collect = collected = cpio_buf;
remains = N_ALIGN(name_len) + body_len;
next_state = GotSymlink;
state = Collect;
return 0;
}
if (S_ISREG(mode) || !body_len)
-   read_into(name_buf, N_ALIGN(name_len), GotName);
+   read_into(cpio_buf, N_ALIGN(name_len), GotName);
return 0;
 }
 
@@ -511,11 +511,16 @@ char * __init unpack_to_rootfs(char *buf, unsigned long 
len)
const char *compress_name;
static __initdata char msg_buf[64];
 
-   header_buf = kmalloc(CPIO_HDRLEN, GFP_KERNEL);
-   /* 2x PATH_MAX as @name_buf is also used for staging symlink targets */
-   name_buf = kmalloc(N_ALIGN(PATH_MAX) + PATH_MAX + 1, GFP_KERNEL);
-   if (!header_buf || !name_buf)
-   panic_show_mem("can't allocate buffers");
+   /*
+* @cpio_buf can be used for staging the 110 byte newc/crc cpio header,
+* after which parse_header() converts and stashes fields into
+* corresponding types. The same buffer can then be reused for file
+* path staging. 2 x PATH_MAX covers any possible symlink target.
+*/
+   BUILD_BUG_ON(CPIO_HDRLEN > N_ALIGN(PATH_MAX) + PATH_MAX + 1);
+   cpio_buf = kmalloc(N_ALIGN(PATH_MAX) + PATH_MAX + 1, GFP_KERNEL);
+   if (!cpio_buf)
+   panic_show_mem("can't allocate buffer");
 
state = Start;
this_header = 0;
@@ -559,8 +564,7 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
len -= my_inptr;
}
dir_utime();
-   kfree(name_buf);
-   kfree(header_buf);
+   kfree(cpio_buf);
return message;
 }
 
-- 
2.43.0




[PATCH v3 7/9] initramfs: reuse name_len for dir mtime tracking

2024-11-06 Thread David Disseldorp
We already have a nulterm-inclusive, checked name_len for the directory
name, so use that instead of calling strlen().

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 7 +++
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 4e2506a4bc76f..c264f136c5281 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -144,9 +144,8 @@ struct dir_entry {
char name[];
 };
 
-static void __init dir_add(const char *name, time64_t mtime)
+static void __init dir_add(const char *name, size_t nlen, time64_t mtime)
 {
-   size_t nlen = strlen(name) + 1;
struct dir_entry *de;
 
de = kmalloc(sizeof(struct dir_entry) + nlen, GFP_KERNEL);
@@ -170,7 +169,7 @@ static void __init dir_utime(void)
 #else
 static void __init do_utime(char *filename, time64_t mtime) {}
 static void __init do_utime_path(const struct path *path, time64_t mtime) {}
-static void __init dir_add(const char *name, time64_t mtime) {}
+static void __init dir_add(const char *name, size_t nlen, time64_t mtime) {}
 static void __init dir_utime(void) {}
 #endif
 
@@ -394,7 +393,7 @@ static int __init do_name(void)
init_mkdir(collected, mode);
init_chown(collected, uid, gid, 0);
init_chmod(collected, mode);
-   dir_add(collected, mtime);
+   dir_add(collected, name_len, mtime);
} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
   S_ISFIFO(mode) || S_ISSOCK(mode)) {
if (maybe_link() == 0) {
-- 
2.43.0




[PATCH v3 5/9] initramfs: remove extra symlink path buffer

2024-11-06 Thread David Disseldorp
A (newc/crc) cpio entry with mode.S_IFLNK set carries the symlink target
in the cpio data segment, following the padded name_len sized file
path. symlink_buf is heap-allocated for staging both file path and
symlink target, while name_buf is additionally allocated for staging
paths for non-symlink cpio entries.

Separate symlink / non-symlink buffers are unnecessary, so just extend
the size of name_buf and use it for both.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 12 +---
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 6dd3b02c15d7e..59b4a43fa491b 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -250,7 +250,7 @@ static void __init read_into(char *buf, unsigned size, enum 
state next)
}
 }
 
-static __initdata char *header_buf, *symlink_buf, *name_buf;
+static __initdata char *header_buf, *name_buf;
 
 static int __init do_start(void)
 {
@@ -294,7 +294,7 @@ static int __init do_header(void)
if (S_ISLNK(mode)) {
if (body_len > PATH_MAX)
return 0;
-   collect = collected = symlink_buf;
+   collect = collected = name_buf;
remains = N_ALIGN(name_len) + body_len;
next_state = GotSymlink;
state = Collect;
@@ -512,10 +512,9 @@ char * __init unpack_to_rootfs(char *buf, unsigned long 
len)
static __initdata char msg_buf[64];
 
header_buf = kmalloc(CPIO_HDRLEN, GFP_KERNEL);
-   symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
-   name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
-
-   if (!header_buf || !symlink_buf || !name_buf)
+   /* 2x PATH_MAX as @name_buf is also used for staging symlink targets */
+   name_buf = kmalloc(N_ALIGN(PATH_MAX) + PATH_MAX + 1, GFP_KERNEL);
+   if (!header_buf || !name_buf)
panic_show_mem("can't allocate buffers");
 
state = Start;
@@ -561,7 +560,6 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
}
dir_utime();
kfree(name_buf);
-   kfree(symlink_buf);
kfree(header_buf);
return message;
 }
-- 
2.43.0




[PATCH v3 9/9] initramfs: avoid static buffer for error message

2024-11-06 Thread David Disseldorp
The dynamic error message printed if CONFIG_RD_$ALG compression support
is missing needn't be propagated up to the caller via a static buffer.
Print it immediately via pr_err() and set @message to a const string to
flag error.

Before:
   textdata bss dec hex filename
   76951102   888052265 ./init/initramfs.o

After:
   textdata bss dec hex filename
   76831006   8869721f9 ./init/initramfs.o

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 10 +++---
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 99f3cac10d392..f946b7680867b 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -511,7 +511,6 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
long written;
decompress_fn decompress;
const char *compress_name;
-   static __initdata char msg_buf[64];
 
/*
 * @cpio_buf can be used for staging the 110 byte newc/crc cpio header,
@@ -551,12 +550,9 @@ char * __init unpack_to_rootfs(char *buf, unsigned long 
len)
if (res)
error("decompressor failed");
} else if (compress_name) {
-   if (!message) {
-   snprintf(msg_buf, sizeof msg_buf,
-"compression method %s not configured",
-compress_name);
-   message = msg_buf;
-   }
+   pr_err("compression method %s not configured\n",
+  compress_name);
+   error("decompressor failed");
} else
error("invalid magic at start of compressed archive");
if (state != Reset)
-- 
2.43.0




[PATCH v3 4/9] initramfs: avoid memcpy for hex header fields

2024-11-06 Thread David Disseldorp
newc/crc cpio headers contain a bunch of 8-character hexadecimal fields
which we convert via simple_strtoul(), following memcpy() into a
zero-terminated stack buffer. The new simple_strntoul() helper allows us
to pass in max_chars=8 to avoid zero-termination and memcpy().

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 9 +++--
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 002e83ae12ac7..6dd3b02c15d7e 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -189,14 +189,11 @@ static __initdata u32 hdr_csum;
 static void __init parse_header(char *s)
 {
unsigned long parsed[13];
-   char buf[9];
int i;
 
-   buf[8] = '\0';
-   for (i = 0, s += 6; i < 13; i++, s += 8) {
-   memcpy(buf, s, 8);
-   parsed[i] = simple_strtoul(buf, NULL, 16);
-   }
+   for (i = 0, s += 6; i < 13; i++, s += 8)
+   parsed[i] = simple_strntoul(s, NULL, 16, 8);
+
ino = parsed[0];
mode = parsed[1];
uid = parsed[2];
-- 
2.43.0




[PATCH v3 8/9] initramfs: fix hardlink hash leak without TRAILER

2024-11-06 Thread David Disseldorp
Covered in Documentation/driver-api/early-userspace/buffer-format.rst ,
initramfs archives can carry an optional "TRAILER!!!" entry which serves
as a boundary for collecting and associating hardlinks with matching
inode and major / minor device numbers.

Although optional, if hardlinks are found in an archive without a
subsequent "TRAILER!!!" entry then the hardlink state hash table is
leaked, e.g. unfixed kernel, with initramfs_test.c hunk applied only:
unreferenced object 0x9405408cc000 (size 8192):
  comm "kunit_try_catch", pid 53, jiffies 4294892519
  hex dump (first 32 bytes):
01 00 00 00 01 00 00 00 00 00 00 00 ff 81 00 00  
00 00 00 00 00 00 00 00 69 6e 69 74 72 61 6d 66  initramf
  backtrace (crc a9fb0ee0):
[<66739faa>] __kmalloc_cache_noprof+0x11d/0x250
[<fc755219>] maybe_link.part.5+0xbc/0x120
[<0526a128>] do_name+0xce/0x2f0
[<145c1048>] write_buffer+0x22/0x40
[<3f0b4f32>] unpack_to_rootfs+0xf9/0x2a0
[<d6f7e5af>] initramfs_test_hardlink+0xe3/0x3f0
[<14fde8d6>] kunit_try_run_case+0x5f/0x130
[<dc9dafc5>] kunit_generic_run_threadfn_adapter+0x18/0x30
[<1076c239>] kthread+0xc8/0x100
[<d939f1c1>] ret_from_fork+0x2b/0x40
[<f848ad1a>] ret_from_fork_asm+0x1a/0x30

Fix this by calling free_hash() after initramfs buffer processing in
unpack_to_rootfs(). An extra hardlink_seen global is added as an
optimization to avoid walking the 32 entry hash array unnecessarily.
The expectation is that a "TRAILER!!!" entry will normally be present,
and initramfs hardlinks are uncommon.

There is one user facing side-effect of this fix: hardlinks can
currently be associated across built-in and external initramfs archives,
*if* the built-in initramfs archive lacks a "TRAILER!!!" terminator. I'd
consider this cross-archive association broken, but perhaps it's used.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c  | 7 ++-
 init/initramfs_test.c | 5 -
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index c264f136c5281..99f3cac10d392 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -76,6 +76,7 @@ static __initdata struct hash {
struct hash *next;
char name[N_ALIGN(PATH_MAX)];
 } *head[32];
+static __initdata bool hardlink_seen;
 
 static inline int hash(int major, int minor, int ino)
 {
@@ -109,19 +110,21 @@ static char __init *find_link(int major, int minor, int 
ino,
strcpy(q->name, name);
q->next = NULL;
*p = q;
+   hardlink_seen = true;
return NULL;
 }
 
 static void __init free_hash(void)
 {
struct hash **p, *q;
-   for (p = head; p < head + 32; p++) {
+   for (p = head; hardlink_seen && p < head + 32; p++) {
while (*p) {
q = *p;
*p = q->next;
kfree(q);
}
}
+   hardlink_seen = false;
 }
 
 #ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
@@ -563,6 +566,8 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
len -= my_inptr;
}
dir_utime();
+   /* free any hardlink state collected without optional TRAILER!!! */
+   free_hash();
kfree(cpio_buf);
return message;
 }
diff --git a/init/initramfs_test.c b/init/initramfs_test.c
index 84b21f465bc3d..a2930c70cc817 100644
--- a/init/initramfs_test.c
+++ b/init/initramfs_test.c
@@ -319,11 +319,6 @@ static void __init initramfs_test_hardlink(struct kunit 
*test)
.namesize = sizeof("initramfs_test_hardlink_link"),
.fname = "initramfs_test_hardlink_link",
.data = "ASDF",
-   }, {
-   /* hardlink hashtable leaks when the archive omits a trailer */
-   .magic = "070701",
-   .namesize = sizeof("TRAILER!!!"),
-   .fname = "TRAILER!!!",
} };
 
cpio_srcbuf = kmalloc(8192, GFP_KERNEL);
-- 
2.43.0




Re: [PATCH v2 2/9] initramfs_test: kunit tests for initramfs unpacking

2024-11-06 Thread David Disseldorp
[adding kunit lists to cc, see lore link below for context]

https://lore.kernel.org/linux-fsdevel/20241104141750.16119-3-dd...@suse.de/

On Wed, 6 Nov 2024 07:04:21 +0800, kernel test robot wrote:
...
> All warnings (new ones prefixed by >>, old ones prefixed by <<):
> 
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x0 (section: .data) -> initramfs_test_extract 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x30 (section: .data) -> initramfs_test_fname_overrun 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x60 (section: .data) -> initramfs_test_data 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x90 (section: .data) -> initramfs_test_csum 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0xc0 (section: .data) -> initramfs_test_hardlink 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0xf0 (section: .data) -> initramfs_test_many 
> >> (section: .init.text)  

If I understand correctly, the kunit_case initramfs_test_cases[]
members can't be flagged __initdata, as they need to be present
post-init for results queries via debugfs.

The remaining -Wimplicit-function-declaration reports are all valid.
I'll fix them in v3.

Thanks, David



Re: [PATCH v3 8/9] initramfs: fix hardlink hash leak without TRAILER

2024-11-26 Thread David Disseldorp
On Thu,  7 Nov 2024 11:17:27 +1100, David Disseldorp wrote:

> Covered in Documentation/driver-api/early-userspace/buffer-format.rst ,
> initramfs archives can carry an optional "TRAILER!!!" entry which serves
> as a boundary for collecting and associating hardlinks with matching
> inode and major / minor device numbers.
> 
> Although optional, if hardlinks are found in an archive without a
> subsequent "TRAILER!!!" entry then the hardlink state hash table is
> leaked

One further leak is possible if extraction ends prior to fput(wfile)
in CopyFile state, e.g. due to lack of data:

  nilchar="\0"
  data="123456789ABCDEF"
  magic="070701"
  ino=1
  mode=$(( 0100777 ))
  uid=0
  gid=0
  nlink=1
  mtime=1
  filesize=$(( ${#data} + 20 ))   # too much
  devmajor=0
  devminor=1
  rdevmajor=0
  rdevminor=0
  csum=0
  fname="initramfs_test_archive_overrun"
  namelen=$(( ${#fname} + 1 ))# plus one to account for terminator
  
  printf "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" \
 $magic $ino $mode $uid $gid $nlink $mtime $filesize \
 $devmajor $devminor $rdevmajor $rdevminor $namelen $csum $fname

  termpadlen=$(( 1 + ((4 - ((110 + $namelen) & 3)) % 4) ))
  printf "%.s${nilchar}" $(seq 1 $termpadlen)
  # $filesize reaches 20 bytes beyond end of data
  printf "%s" "$data"

bash data_repro.sh|gzip >> initramfs

unreferenced object 0x8fdb0192e000 (size 176):
  comm "kworker/u8:0", pid 11, jiffies 4294892503
  hex dump (first 32 bytes):
01 00 00 00 00 00 00 00 00 00 00 00 1e 80 5d 00  ..].
80 7d a1 a7 ff ff ff ff 10 b1 2f 02 db 8f ff ff  .}/.
  backtrace (crc 807bd733):
[<e68e8b32>] kmem_cache_alloc_noprof+0x11e/0x260
[<a6f24fcd>] alloc_empty_file+0x45/0x120
[<130beec8>] path_openat+0x2f/0xf30
[<24613ad7>] do_filp_open+0xa7/0x110
[<5f4f0158>] file_open_name+0x118/0x180
[<03ed573f>] filp_open+0x27/0x50
[<91ec9e44>] do_name+0xc4/0x2b0
[<8e084ec8>] write_buffer+0x22/0x40
[<2ea2ff4b>] flush_buffer+0x2f/0x90
[<9085f8b5>] gunzip+0x25a/0x310
[<0c1c83c3>] unpack_to_rootfs+0x176/0x2a0
[<c966fda5>] do_populate_rootfs+0x6a/0x180
[<51fb877d>] async_run_entry_fn+0x31/0x120
[<a3ee305f>] process_scheduled_works+0xbe/0x310
[<83c835bb>] worker_thread+0x100/0x240
[<6ea2f0b3>] kthread+0xc8/0x100

Not sure whether others are interested in seeing these kinds of
leak-on-malformed-archive bugs fixed, but I'll send through a v4 with a
fix + unit test for it.



Re: [brauner-github:vfs.all 205/231] WARNING: modpost: vmlinux: section mismatch in reference: initramfs_test_cases+0x0 (section: .data) -> initramfs_test_extract (section: .init.text)

2025-03-04 Thread David Disseldorp
[cc'ing linux-kselftest and kunit-dev]

Hi,

On Wed, 5 Mar 2025 01:47:55 +0800, kernel test robot wrote:

> tree:   https://github.com/brauner/linux.git vfs.all
> head:   ea47e99a3a234837d5fea0d1a20bb2ad1eaa6dd4
> commit: b6736cfccb582b7c016cba6cd484fbcf30d499af [205/231] initramfs_test: 
> kunit tests for initramfs unpacking
> config: x86_64-buildonly-randconfig-002-20250304 
> (https://download.01.org/0day-ci/archive/20250305/202503050109.t5ab93hx-...@intel.com/config)
> compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
> reproduce (this is a W=1 build): 
> (https://download.01.org/0day-ci/archive/20250305/202503050109.t5ab93hx-...@intel.com/reproduce)
> 
> If you fix the issue in a separate patch/commit (i.e. not just a new version 
> of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot 
> | Closes: 
> https://lore.kernel.org/oe-kbuild-all/202503050109.t5ab93hx-...@intel.com/
> 
> All warnings (new ones prefixed by >>, old ones prefixed by <<):
> 
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x0 (section: .data) -> initramfs_test_extract 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x30 (section: .data) -> initramfs_test_fname_overrun 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x60 (section: .data) -> initramfs_test_data 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0x90 (section: .data) -> initramfs_test_csum 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0xc0 (section: .data) -> initramfs_test_hardlink 
> >> (section: .init.text)
> >> WARNING: modpost: vmlinux: section mismatch in reference: 
> >> initramfs_test_cases+0xf0 (section: .data) -> initramfs_test_many 
> >> (section: .init.text)  

These new warnings are covered in the commit message. The
kunit_test_init_section_suites() registered tests aren't in the .init
section as debugfs entries are retained for results reporting (without
an ability to rerun them).
IIUC, the __kunit_init_test_suites->CONCATENATE(..., _probe) suffix is
intended to suppress the modpost warning - @kunit-dev: any ideas why
this isn't working as intended?

Thanks, David



[PATCH v4 7/8] initramfs: fix hardlink hash leak without TRAILER

2025-03-03 Thread David Disseldorp
Covered in Documentation/driver-api/early-userspace/buffer-format.rst ,
initramfs archives can carry an optional "TRAILER!!!" entry which serves
as a boundary for collecting and associating hardlinks with matching
inode and major / minor device numbers.

Although optional, if hardlinks are found in an archive without a
subsequent "TRAILER!!!" entry then the hardlink state hash table is
leaked, e.g. unfixed kernel, with initramfs_test.c hunk applied only:
unreferenced object 0x9405408cc000 (size 8192):
  comm "kunit_try_catch", pid 53, jiffies 4294892519
  hex dump (first 32 bytes):
01 00 00 00 01 00 00 00 00 00 00 00 ff 81 00 00  
00 00 00 00 00 00 00 00 69 6e 69 74 72 61 6d 66  initramf
  backtrace (crc a9fb0ee0):
[<66739faa>] __kmalloc_cache_noprof+0x11d/0x250
[<fc755219>] maybe_link.part.5+0xbc/0x120
[<0526a128>] do_name+0xce/0x2f0
[<145c1048>] write_buffer+0x22/0x40
[<3f0b4f32>] unpack_to_rootfs+0xf9/0x2a0
[<d6f7e5af>] initramfs_test_hardlink+0xe3/0x3f0
[<14fde8d6>] kunit_try_run_case+0x5f/0x130
[<dc9dafc5>] kunit_generic_run_threadfn_adapter+0x18/0x30
[<1076c239>] kthread+0xc8/0x100
[<d939f1c1>] ret_from_fork+0x2b/0x40
[<f848ad1a>] ret_from_fork_asm+0x1a/0x30

Fix this by calling free_hash() after initramfs buffer processing in
unpack_to_rootfs(). An extra hardlink_seen global is added as an
optimization to avoid walking the 32 entry hash array unnecessarily.
The expectation is that a "TRAILER!!!" entry will normally be present,
and initramfs hardlinks are uncommon.

There is one user facing side-effect of this fix: hardlinks can
currently be associated across built-in and external initramfs archives,
*if* the built-in initramfs archive lacks a "TRAILER!!!" terminator. I'd
consider this cross-archive association broken, but perhaps it's used.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 7 ++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index b9cacdc54eaf1..e0b11f8d6f3d6 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -76,6 +76,7 @@ static __initdata struct hash {
struct hash *next;
char name[N_ALIGN(PATH_MAX)];
 } *head[32];
+static __initdata bool hardlink_seen;
 
 static inline int hash(int major, int minor, int ino)
 {
@@ -109,19 +110,21 @@ static char __init *find_link(int major, int minor, int 
ino,
strcpy(q->name, name);
q->next = NULL;
*p = q;
+   hardlink_seen = true;
return NULL;
 }
 
 static void __init free_hash(void)
 {
struct hash **p, *q;
-   for (p = head; p < head + 32; p++) {
+   for (p = head; hardlink_seen && p < head + 32; p++) {
while (*p) {
q = *p;
*p = q->next;
kfree(q);
}
}
+   hardlink_seen = false;
 }
 
 #ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
@@ -564,6 +567,8 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
len -= my_inptr;
}
dir_utime();
+   /* free any hardlink state collected without optional TRAILER!!! */
+   free_hash();
kfree(bufs);
return message;
 }
-- 
2.43.0




[PATCH v4 5/8] initramfs: allocate heap buffers together

2025-03-03 Thread David Disseldorp
header_buf, symlink_buf and name_buf all share the same lifecycle so
needn't be allocated / freed separately. This change leads to a minor
reduction in .text size:

before:
   textdata bss dec hex filename
   79141110   890322348 init/initramfs.o

after:
   textdata bss dec hex filename
   78541110   88972230c init/initramfs.o

A previous iteration of this patch reused a single buffer instead of
three, given that buffer use is state-sequential (GotHeader, GotName,
GotSymlink). However, the slight decrease in heap use during early boot
isn't really worth the extra review complexity.

Link: https://lore.kernel.org/all/20241107002044.16477-7-dd...@suse.de/
Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 19 +++
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 6dd3b02c15d7e..df75624cdefbf 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -510,14 +510,19 @@ char * __init unpack_to_rootfs(char *buf, unsigned long 
len)
decompress_fn decompress;
const char *compress_name;
static __initdata char msg_buf[64];
+   struct {
+   char header[CPIO_HDRLEN];
+   char symlink[PATH_MAX + N_ALIGN(PATH_MAX) + 1];
+   char name[N_ALIGN(PATH_MAX)];
+   } *bufs = kmalloc(sizeof(*bufs), GFP_KERNEL);
 
-   header_buf = kmalloc(CPIO_HDRLEN, GFP_KERNEL);
-   symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
-   name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
-
-   if (!header_buf || !symlink_buf || !name_buf)
+   if (!bufs)
panic_show_mem("can't allocate buffers");
 
+   header_buf = bufs->header;
+   symlink_buf = bufs->symlink;
+   name_buf = bufs->name;
+
state = Start;
this_header = 0;
message = NULL;
@@ -560,9 +565,7 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
len -= my_inptr;
}
dir_utime();
-   kfree(name_buf);
-   kfree(symlink_buf);
-   kfree(header_buf);
+   kfree(bufs);
return message;
 }
 
-- 
2.43.0




[PATCH v4 2/8] initramfs_test: kunit tests for initramfs unpacking

2025-03-03 Thread David Disseldorp
Provide some basic initramfs unpack sanity tests covering:
- simple file / dir extraction
- filename field overrun, as reported and fixed separately via
  https://lore.kernel.org/r/20241030035509.20194-2-dd...@suse.de
- "070702" cpio data checksums
- hardlinks

These tests introduce new modpost warnings for initramfs_test_cases
section=.data -> section=.init.text run_case() hooks. The
kunit_case/_suite struct cannot be marked as __initdata as this
will be used in debugfs to retrieve results after a test has run.

Signed-off-by: David Disseldorp 
---
 init/.kunitconfig |   3 +
 init/Kconfig  |   7 +
 init/Makefile |   1 +
 init/initramfs_test.c | 407 ++
 4 files changed, 418 insertions(+)
 create mode 100644 init/.kunitconfig
 create mode 100644 init/initramfs_test.c

diff --git a/init/.kunitconfig b/init/.kunitconfig
new file mode 100644
index 0..acb906b1a5f98
--- /dev/null
+++ b/init/.kunitconfig
@@ -0,0 +1,3 @@
+CONFIG_KUNIT=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_INITRAMFS_TEST=y
diff --git a/init/Kconfig b/init/Kconfig
index d0d021b3fa3b3..ded64f6c949ed 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1454,6 +1454,13 @@ config INITRAMFS_PRESERVE_MTIME
 
  If unsure, say Y.
 
+config INITRAMFS_TEST
+   bool "Test initramfs cpio archive extraction" if !KUNIT_ALL_TESTS
+   depends on BLK_DEV_INITRD && KUNIT=y
+   default KUNIT_ALL_TESTS
+   help
+ Build KUnit tests for initramfs. See Documentation/dev-tools/kunit
+
 choice
prompt "Compiler optimization level"
default CC_OPTIMIZE_FOR_PERFORMANCE
diff --git a/init/Makefile b/init/Makefile
index 10b652d33e872..d6f75d8907e09 100644
--- a/init/Makefile
+++ b/init/Makefile
@@ -12,6 +12,7 @@ else
 obj-$(CONFIG_BLK_DEV_INITRD)   += initramfs.o
 endif
 obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
+obj-$(CONFIG_INITRAMFS_TEST)   += initramfs_test.o
 
 obj-y  += init_task.o
 
diff --git a/init/initramfs_test.c b/init/initramfs_test.c
new file mode 100644
index 0..6231fe0125831
--- /dev/null
+++ b/init/initramfs_test.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "initramfs_internal.h"
+
+struct initramfs_test_cpio {
+   char *magic;
+   unsigned int ino;
+   unsigned int mode;
+   unsigned int uid;
+   unsigned int gid;
+   unsigned int nlink;
+   unsigned int mtime;
+   unsigned int filesize;
+   unsigned int devmajor;
+   unsigned int devminor;
+   unsigned int rdevmajor;
+   unsigned int rdevminor;
+   unsigned int namesize;
+   unsigned int csum;
+   char *fname;
+   char *data;
+};
+
+static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out)
+{
+   int i;
+   size_t off = 0;
+
+   for (i = 0; i < csz; i++) {
+   char *pos = &out[off];
+   struct initramfs_test_cpio *c = &cs[i];
+   size_t thislen;
+
+   /* +1 to account for nulterm */
+   thislen = sprintf(pos, "%s"
+   "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x"
+   "%s",
+   c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink,
+   c->mtime, c->filesize, c->devmajor, c->devminor,
+   c->rdevmajor, c->rdevminor, c->namesize, c->csum,
+   c->fname) + 1;
+   pr_debug("packing (%zu): %.*s\n", thislen, (int)thislen, pos);
+   off += thislen;
+   while (off & 3)
+   out[off++] = '\0';
+
+   memcpy(&out[off], c->data, c->filesize);
+   off += c->filesize;
+   while (off & 3)
+   out[off++] = '\0';
+   }
+
+   return off;
+}
+
+static void __init initramfs_test_extract(struct kunit *test)
+{
+   char *err, *cpio_srcbuf;
+   size_t len;
+   struct timespec64 ts_before, ts_after;
+   struct kstat st = {};
+   struct initramfs_test_cpio c[] = { {
+   .magic = "070701",
+   .ino = 1,
+   .mode = S_IFREG | 0777,
+   .uid = 12,
+   .gid = 34,
+   .nlink = 1,
+   .mtime = 56,
+   .filesize = 0,
+   .devmajor = 0,
+   .devminor = 1,
+   .rdevmajor = 0,
+   .rdevminor = 0,
+   .namesize = sizeof("initramfs_test_extract"),
+   .csum = 0,
+   .fname = "initramfs_test_extract",
+   }, {
+   .magic = "070701",
+ 

[PATCH v4 1/8] init: add initramfs_internal.h

2025-03-03 Thread David Disseldorp
The new header only exports a single unpack function and a CPIO_HDRLEN
constant for future test use.

Signed-off-by: David Disseldorp 
---
 init/initramfs.c  | 16 +---
 init/initramfs_internal.h |  8 
 2 files changed, 21 insertions(+), 3 deletions(-)
 create mode 100644 init/initramfs_internal.h

diff --git a/init/initramfs.c b/init/initramfs.c
index b2f7583bb1f5c..002e83ae12ac7 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -20,6 +20,7 @@
 #include 
 
 #include "do_mounts.h"
+#include "initramfs_internal.h"
 
 static __initdata bool csum_present;
 static __initdata u32 io_csum;
@@ -256,7 +257,7 @@ static __initdata char *header_buf, *symlink_buf, *name_buf;
 
 static int __init do_start(void)
 {
-   read_into(header_buf, 110, GotHeader);
+   read_into(header_buf, CPIO_HDRLEN, GotHeader);
return 0;
 }
 
@@ -497,14 +498,23 @@ static unsigned long my_inptr __initdata; /* index of 
next byte to be processed
 
 #include 
 
-static char * __init unpack_to_rootfs(char *buf, unsigned long len)
+/**
+ * unpack_to_rootfs - decompress and extract an initramfs archive
+ * @buf: input initramfs archive to extract
+ * @len: length of initramfs data to process
+ *
+ * Returns: NULL for success or an error message string
+ *
+ * This symbol shouldn't be used externally. It's available for unit tests.
+ */
+char * __init unpack_to_rootfs(char *buf, unsigned long len)
 {
long written;
decompress_fn decompress;
const char *compress_name;
static __initdata char msg_buf[64];
 
-   header_buf = kmalloc(110, GFP_KERNEL);
+   header_buf = kmalloc(CPIO_HDRLEN, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
 
diff --git a/init/initramfs_internal.h b/init/initramfs_internal.h
new file mode 100644
index 0..233dad16b0a00
--- /dev/null
+++ b/init/initramfs_internal.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef __INITRAMFS_INTERNAL_H__
+#define __INITRAMFS_INTERNAL_H__
+
+char *unpack_to_rootfs(char *buf, unsigned long len);
+#define CPIO_HDRLEN 110
+
+#endif
-- 
2.43.0




[PATCH v4 4/8] initramfs: avoid memcpy for hex header fields

2025-03-03 Thread David Disseldorp
newc/crc cpio headers contain a bunch of 8-character hexadecimal fields
which we convert via simple_strtoul(), following memcpy() into a
zero-terminated stack buffer. The new simple_strntoul() helper allows us
to pass in max_chars=8 to avoid zero-termination and memcpy().

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 9 +++--
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index 002e83ae12ac7..6dd3b02c15d7e 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -189,14 +189,11 @@ static __initdata u32 hdr_csum;
 static void __init parse_header(char *s)
 {
unsigned long parsed[13];
-   char buf[9];
int i;
 
-   buf[8] = '\0';
-   for (i = 0, s += 6; i < 13; i++, s += 8) {
-   memcpy(buf, s, 8);
-   parsed[i] = simple_strtoul(buf, NULL, 16);
-   }
+   for (i = 0, s += 6; i < 13; i++, s += 8)
+   parsed[i] = simple_strntoul(s, NULL, 16, 8);
+
ino = parsed[0];
mode = parsed[1];
uid = parsed[2];
-- 
2.43.0




[PATCH v4 8/8] initramfs: avoid static buffer for error message

2025-03-03 Thread David Disseldorp
The dynamic error message printed if CONFIG_RD_$ALG compression support
is missing needn't be propagated up to the caller via a static buffer.
Print it immediately via pr_err() and set @message to a const string to
flag error.

Before:
   textdata bss dec hex filename
   80061118   8913223ac init/initramfs.o

After:
   textdata bss dec hex filename
   79381022   889682308 init/initramfs.o

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 10 +++---
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index e0b11f8d6f3d6..72bad44a1d418 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -511,7 +511,6 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
long written;
decompress_fn decompress;
const char *compress_name;
-   static __initdata char msg_buf[64];
struct {
char header[CPIO_HDRLEN];
char symlink[PATH_MAX + N_ALIGN(PATH_MAX) + 1];
@@ -552,12 +551,9 @@ char * __init unpack_to_rootfs(char *buf, unsigned long 
len)
if (res)
error("decompressor failed");
} else if (compress_name) {
-   if (!message) {
-   snprintf(msg_buf, sizeof msg_buf,
-"compression method %s not configured",
-compress_name);
-   message = msg_buf;
-   }
+   pr_err("compression method %s not configured\n",
+  compress_name);
+   error("decompressor failed");
} else
error("invalid magic at start of compressed archive");
if (state != Reset)
-- 
2.43.0




[PATCH v4 0/8] initramfs: kunit tests and cleanups

2025-03-03 Thread David Disseldorp
This patchset adds basic kunit test coverage for initramfs unpacking
and cleans up some minor buffer handling issues / inefficiencies.

Changes since v3:
- Drop shared unpack buffer changes
  + rework into initramfs: allocate heap buffers together (patch 5/8)
  + extra review complexity wasn't worth the tiny boot-time heap saving
- move hardlink hash leak repro into first initramfs_test patch
- add note regarding kunit section=.data -> section=.init.text warning

Changes since v2 (patch 2 only):
- fix !CONFIG_INITRAMFS_PRESERVE_MTIME kunit test checks
- add test MODULE_DESCRIPTION(), as suggested by Jeff Johnson
- add some missing headers, reported by kernel test robot

Changes since v1 (RFC):
- rebase atop v6.12-rc6 and filename field overrun fix from
  https://lore.kernel.org/r/20241030035509.20194-2-dd...@suse.de
- add unit test coverage (new patches 1 and 2)
- add patch: fix hardlink hash leak without TRAILER
- rework patch: avoid static buffer for error message
  + drop unnecessary message propagation
- drop patch: cpio_buf reuse for built-in and bootloader initramfs
  + no good justification for the change

Feedback appreciated.

David Disseldorp (8):
  init: add initramfs_internal.h
  initramfs_test: kunit tests for initramfs unpacking
  vsprintf: add simple_strntoul
  initramfs: avoid memcpy for hex header fields
  initramfs: allocate heap buffers together
  initramfs: reuse name_len for dir mtime tracking
  initramfs: fix hardlink hash leak without TRAILER
  initramfs: avoid static buffer for error message

 include/linux/kstrtox.h   |   1 +
 init/.kunitconfig |   3 +
 init/Kconfig  |   7 +
 init/Makefile |   1 +
 init/initramfs.c  |  66 
 init/initramfs_internal.h |   8 +
 init/initramfs_test.c | 407 ++
 lib/vsprintf.c|   7 +
 8 files changed, 472 insertions(+), 28 deletions(-)
 create mode 100644 init/.kunitconfig
 create mode 100644 init/initramfs_internal.h
 create mode 100644 init/initramfs_test.c



[PATCH v4 6/8] initramfs: reuse name_len for dir mtime tracking

2025-03-03 Thread David Disseldorp
We already have a nulterm-inclusive, checked name_len for the directory
name, so use that instead of calling strlen().

Signed-off-by: David Disseldorp 
---
 init/initramfs.c | 7 +++
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index df75624cdefbf..b9cacdc54eaf1 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -144,9 +144,8 @@ struct dir_entry {
char name[];
 };
 
-static void __init dir_add(const char *name, time64_t mtime)
+static void __init dir_add(const char *name, size_t nlen, time64_t mtime)
 {
-   size_t nlen = strlen(name) + 1;
struct dir_entry *de;
 
de = kmalloc(sizeof(struct dir_entry) + nlen, GFP_KERNEL);
@@ -170,7 +169,7 @@ static void __init dir_utime(void)
 #else
 static void __init do_utime(char *filename, time64_t mtime) {}
 static void __init do_utime_path(const struct path *path, time64_t mtime) {}
-static void __init dir_add(const char *name, time64_t mtime) {}
+static void __init dir_add(const char *name, size_t nlen, time64_t mtime) {}
 static void __init dir_utime(void) {}
 #endif
 
@@ -394,7 +393,7 @@ static int __init do_name(void)
init_mkdir(collected, mode);
init_chown(collected, uid, gid, 0);
init_chmod(collected, mode);
-   dir_add(collected, mtime);
+   dir_add(collected, name_len, mtime);
} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
   S_ISFIFO(mode) || S_ISSOCK(mode)) {
if (maybe_link() == 0) {
-- 
2.43.0




[PATCH v4 3/8] vsprintf: add simple_strntoul

2025-03-03 Thread David Disseldorp
cpio extraction currently does a memcpy to ensure that the archive hex
fields are null terminated for simple_strtoul(). simple_strntoul() will
allow us to avoid the memcpy.

Signed-off-by: David Disseldorp 
---
 include/linux/kstrtox.h | 1 +
 lib/vsprintf.c  | 7 +++
 2 files changed, 8 insertions(+)

diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index 7fcf29a4e0de4..6ea897222af1d 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -143,6 +143,7 @@ static inline int __must_check kstrtos32_from_user(const 
char __user *s, size_t
  */
 
 extern unsigned long simple_strtoul(const char *,char **,unsigned int);
+extern unsigned long simple_strntoul(const char *,char **,unsigned int,size_t);
 extern long simple_strtol(const char *,char **,unsigned int);
 extern unsigned long long simple_strtoull(const char *,char **,unsigned int);
 extern long long simple_strtoll(const char *,char **,unsigned int);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 56fe963192926..734bd70c8b9b3 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -114,6 +114,13 @@ unsigned long simple_strtoul(const char *cp, char **endp, 
unsigned int base)
 }
 EXPORT_SYMBOL(simple_strtoul);
 
+unsigned long simple_strntoul(const char *cp, char **endp, unsigned int base,
+ size_t max_chars)
+{
+   return simple_strntoull(cp, endp, base, max_chars);
+}
+EXPORT_SYMBOL(simple_strntoul);
+
 /**
  * simple_strtol - convert a string to a signed long
  * @cp: The start of the string
-- 
2.43.0




Re: [brauner-github:vfs.all 205/231] WARNING: modpost: vmlinux: section mismatch in reference: initramfs_test_cases+0x0 (section: .data) -> initramfs_test_extract (section: .init.text)

2025-03-05 Thread David Disseldorp
On Wed, 5 Mar 2025 11:47:01 +1100, David Disseldorp wrote:

> [cc'ing linux-kselftest and kunit-dev]
> 
> Hi,
> 
> On Wed, 5 Mar 2025 01:47:55 +0800, kernel test robot wrote:
> 
> > tree:   https://github.com/brauner/linux.git vfs.all
> > head:   ea47e99a3a234837d5fea0d1a20bb2ad1eaa6dd4
> > commit: b6736cfccb582b7c016cba6cd484fbcf30d499af [205/231] initramfs_test: 
> > kunit tests for initramfs unpacking
> > config: x86_64-buildonly-randconfig-002-20250304 
> > (https://download.01.org/0day-ci/archive/20250305/202503050109.t5ab93hx-...@intel.com/config)
> > compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
> > reproduce (this is a W=1 build): 
> > (https://download.01.org/0day-ci/archive/20250305/202503050109.t5ab93hx-...@intel.com/reproduce)
> > 
> > If you fix the issue in a separate patch/commit (i.e. not just a new 
> > version of
> > the same patch/commit), kindly add following tags
> > | Reported-by: kernel test robot 
> > | Closes: 
> > https://lore.kernel.org/oe-kbuild-all/202503050109.t5ab93hx-...@intel.com/
> > 
> > All warnings (new ones prefixed by >>, old ones prefixed by <<):
> >   
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0x0 (section: .data) -> initramfs_test_extract 
> > >> (section: .init.text)
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0x30 (section: .data) -> 
> > >> initramfs_test_fname_overrun (section: .init.text)
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0x60 (section: .data) -> initramfs_test_data 
> > >> (section: .init.text)
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0x90 (section: .data) -> initramfs_test_csum 
> > >> (section: .init.text)
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0xc0 (section: .data) -> initramfs_test_hardlink 
> > >> (section: .init.text)
> > >> WARNING: modpost: vmlinux: section mismatch in reference: 
> > >> initramfs_test_cases+0xf0 (section: .data) -> initramfs_test_many 
> > >> (section: .init.text)
> 
> These new warnings are covered in the commit message. The
> kunit_test_init_section_suites() registered tests aren't in the .init
> section as debugfs entries are retained for results reporting (without
> an ability to rerun them).
> IIUC, the __kunit_init_test_suites->CONCATENATE(..., _probe) suffix is
> intended to suppress the modpost warning - @kunit-dev: any ideas why
> this isn't working as intended?

Stephen Rothwell (cc'ed) mentioned that we might be able to use
__refdata for initramfs_test_cases. The __ref* description in init.h
does indicate that it's suitable, and I now see that it's present in
kunit-example-test.c . I'll propose a patch which can be squashed in
with the existing commit.

Thanks, David



[PATCH] initramfs_test: flag kunit_case __refdata to suppress warning

2025-03-05 Thread David Disseldorp
We already have a comment regarding why .data -> .init references are
present for initramfs_test_cases[]. This allows us to suppress the
modpost warning.

Reported-by: kernel test robot 
Closes: 
https://lore.kernel.org/oe-kbuild-all/202503050109.t5ab93hx-...@intel.com/
Suggested-by: Stephen Rothwell 
Signed-off-by: David Disseldorp 
---

@Christian: feel free to squash this in with commit b6736cfccb582
("initramfs_test: kunit tests for initramfs unpacking") in your
vfs-6.15.initramfs branch if you like (and remove "These tests introduce
new modpost warnings..." from the commit msg).

 init/initramfs_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/init/initramfs_test.c b/init/initramfs_test.c
index 6231fe0125831..696fff583ee42 100644
--- a/init/initramfs_test.c
+++ b/init/initramfs_test.c
@@ -387,7 +387,7 @@ static void __init initramfs_test_many(struct kunit *test)
  * The kunit_case/_suite struct cannot be marked as __initdata as this will be
  * used in debugfs to retrieve results after test has run.
  */
-static struct kunit_case initramfs_test_cases[] = {
+static struct kunit_case __refdata initramfs_test_cases[] = {
KUNIT_CASE(initramfs_test_extract),
KUNIT_CASE(initramfs_test_fname_overrun),
KUNIT_CASE(initramfs_test_data),
-- 
2.43.0