From 8d14fe78cb5ac5b7ac85f5fbf0be9afb3a3dba0e Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 31 Mar 2026 17:57:31 +1100 Subject: [PATCH 1/6] initramfs_test: add fill_cpio() inject_ox parameter fill_cpio() uses sprintf() to write out the in-memory cpio archive from an array of struct initramfs_test_cpio. This change allows callers to modify the cpio sprintf() format string so that future tests can intentionally corrupt the header with "0x" and "0X" prefixed fields. Signed-off-by: David Disseldorp Link: https://patch.msgid.link/20260331070519.5974-2-ddiss@suse.de Reviewed-by: Andy Shevchenko Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs_test.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 2ce38d9a8fd0..6fe1c44a74a5 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -27,7 +27,18 @@ struct initramfs_test_cpio { char *data; }; -static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) +/* regular newc header format */ +#define CPIO_HDR_FMT "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" +/* + * Bogus newc header with "0x" prefixes on the uid, gid, and namesize values. + * parse_header()/simple_str[n]toul() accept this, contrary to the initramfs + * specification. + */ +#define CPIO_HDR_OX_INJECT \ + "%s%08x%08x0x%06x0X%06x%08x%08x%08x%08x%08x%08x%08x0x%06x%08x%s" + +static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, + bool inject_ox, char *out) { int i; size_t off = 0; @@ -38,9 +49,8 @@ static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) 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", + thislen = sprintf(pos, + inject_ox ? CPIO_HDR_OX_INJECT : CPIO_HDR_FMT, 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, @@ -102,7 +112,7 @@ static void __init initramfs_test_extract(struct kunit *test) /* +3 to cater for any 4-byte end-alignment */ cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3), GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); ktime_get_real_ts64(&ts_before); err = unpack_to_rootfs(cpio_srcbuf, len); @@ -177,7 +187,7 @@ static void __init initramfs_test_fname_overrun(struct kunit *test) /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */ cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0'; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); /* overwrite trailing fname terminator and padding */ suffix_off = len - 1; while (cpio_srcbuf[suffix_off] == '\0') { @@ -219,7 +229,7 @@ static void __init initramfs_test_data(struct kunit *test) cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -274,7 +284,7 @@ static void __init initramfs_test_csum(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -284,7 +294,7 @@ static void __init initramfs_test_csum(struct kunit *test) /* mess up the csum and confirm that unpack fails */ c[0].csum--; - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NOT_NULL(test, err); @@ -330,7 +340,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) cpio_srcbuf = kmalloc(8192, GFP_KERNEL); - len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, cpio_srcbuf); err = unpack_to_rootfs(cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -371,7 +381,7 @@ static void __init initramfs_test_many(struct kunit *test) }; c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i); - p += fill_cpio(&c, 1, p); + p += fill_cpio(&c, 1, false, p); } len = p - cpio_srcbuf; @@ -425,7 +435,7 @@ static void __init initramfs_test_fname_pad(struct kunit *test) } }; memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname")); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_srcbuf); err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); KUNIT_EXPECT_NULL(test, err); @@ -481,7 +491,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) memcpy(tbufs->fname_oversize, "fname_oversize", sizeof("fname_oversize") - 1); memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1); - len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_src); + len = fill_cpio(c, ARRAY_SIZE(c), false, tbufs->cpio_src); /* unpack skips over fname_oversize instead of returning an error */ err = unpack_to_rootfs(tbufs->cpio_src, len); From 19868f7034a1c5a548d70f20a35ad3562ac69e2c Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 31 Mar 2026 17:57:32 +1100 Subject: [PATCH 2/6] initramfs_test: test header fields with 0x hex prefix cpio header fields are 8-byte hex strings, but one "interesting" side-effect of our historic simple_str[n]toul() use means that a "0x" (or "0X") prefixed header field will be successfully processed when coupled alongside a 6-byte hex remainder string. "0x" prefix support is contrary to the initramfs specification at Documentation/driver-api/early-userspace/buffer-format.rst which states: The structure of the cpio_header is as follows (all fields contain hexadecimal ASCII numbers fully padded with '0' on the left to the full width of the field, for example, the integer 4780 is represented by the ASCII string "000012ac"): Test for this corner case by injecting "0x" prefixes into the uid, gid and namesize cpio header fields. Confirm that init_stat() returns matching uid and gid values. This test can be modified in future to expect unpack_to_rootfs() failure when header validation is changed to properly follow the specification. Add some missing struct kstat initializations to account for possible init_stat() failures. Signed-off-by: David Disseldorp Link: https://patch.msgid.link/20260331070519.5974-3-ddiss@suse.de Reviewed-by: Petr Mladek Reviewed-by: Andy Shevchenko Signed-off-by: Christian Brauner --- init/initramfs_test.c | 60 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/init/initramfs_test.c b/init/initramfs_test.c index 6fe1c44a74a5..b9f83dc194aa 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -316,7 +316,7 @@ static void __init initramfs_test_hardlink(struct kunit *test) { char *err, *cpio_srcbuf; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; struct initramfs_test_cpio c[] = { { .magic = "070701", .ino = 1, @@ -461,7 +461,7 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) { char *err; size_t len; - struct kstat st0, st1; + struct kstat st0 = {}, st1 = {}; char fdata[] = "this file data will not be unpacked"; struct test_fname_path_max { char fname_oversize[PATH_MAX + 1]; @@ -504,6 +504,61 @@ static void __init initramfs_test_fname_path_max(struct kunit *test) kfree(tbufs); } +static void __init initramfs_test_hdr_hex(struct kunit *test) +{ + char *err; + size_t len; + struct kstat st0 = {}, st1 = {}; + char fdata[] = "this file data will be unpacked"; + struct initramfs_test_bufs { + char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; + } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); + struct initramfs_test_cpio c[] = { { + .magic = "070701", + .ino = 1, + .mode = S_IFREG | 0777, + .uid = 0x123456, + .gid = 0x123457, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_0"), + .fname = "initramfs_test_hdr_hex_0", + .filesize = sizeof(fdata), + .data = fdata, + }, { + .magic = "070701", + .ino = 2, + .mode = S_IFDIR | 0777, + .uid = 0x000056, + .gid = 0x000057, + .nlink = 1, + .namesize = sizeof("initramfs_test_hdr_hex_1"), + .fname = "initramfs_test_hdr_hex_1", + } }; + + /* inject_ox=true to add "0x" cpio field prefixes */ + len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); + + err = unpack_to_rootfs(tbufs->cpio_src, len); + KUNIT_EXPECT_NULL(test, err); + + KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); + KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); + + KUNIT_EXPECT_TRUE(test, + uid_eq(st0.uid, make_kuid(current_user_ns(), (uid_t)0x123456))); + KUNIT_EXPECT_TRUE(test, + gid_eq(st0.gid, make_kgid(current_user_ns(), (gid_t)0x123457))); + KUNIT_EXPECT_TRUE(test, + uid_eq(st1.uid, make_kuid(current_user_ns(), (uid_t)0x56))); + KUNIT_EXPECT_TRUE(test, + gid_eq(st1.gid, make_kgid(current_user_ns(), (gid_t)0x57))); + + KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); + KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); + + kfree(tbufs); +} + /* * The kunit_case/_suite struct cannot be marked as __initdata as this will be * used in debugfs to retrieve results after test has run. @@ -517,6 +572,7 @@ static struct kunit_case __refdata initramfs_test_cases[] = { KUNIT_CASE(initramfs_test_many), KUNIT_CASE(initramfs_test_fname_pad), KUNIT_CASE(initramfs_test_fname_path_max), + KUNIT_CASE(initramfs_test_hdr_hex), {}, }; From a4d6170e86c72fad45257e189bbd901f64dfb40a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:33 +1100 Subject: [PATCH 3/6] initramfs: Sort headers alphabetically Sorting headers alphabetically helps locating duplicates, and makes it easier to figure out where to insert new headers. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-4-ddiss@suse.de Reviewed-by: David Disseldorp Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/init/initramfs.c b/init/initramfs.c index 58db15fb18fd..bf9664fdd8fe 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -1,25 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 -#include #include -#include -#include -#include -#include -#include #include -#include #include -#include -#include +#include +#include #include +#include +#include +#include #include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include #include "do_mounts.h" #include "initramfs_internal.h" From ec03d259f67bab506dc176a661210a3ee15e31b6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:34 +1100 Subject: [PATCH 4/6] initramfs: Refactor to use hex2bin() instead of custom approach There is a simple_strntoul() function used solely as a shortcut for hex2bin() with proper endianess conversions. Replace that and drop the unneeded function in the next changes. This implementation will abort if we fail to parse the cpio header, instead of using potentially bogus header values. Co-developed-by: David Disseldorp Signed-off-by: David Disseldorp Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-5-ddiss@suse.de Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- init/initramfs.c | 44 +++++++++++++++++++++++++------------------ init/initramfs_test.c | 24 ++++------------------- 2 files changed, 30 insertions(+), 38 deletions(-) diff --git a/init/initramfs.c b/init/initramfs.c index bf9664fdd8fe..20a18fcda48e 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,8 @@ #include #include +#include + #include "do_mounts.h" #include "initramfs_internal.h" @@ -190,26 +193,30 @@ static __initdata gid_t gid; static __initdata unsigned rdev; static __initdata u32 hdr_csum; -static void __init parse_header(char *s) +static int __init parse_header(char *s) { - unsigned long parsed[13]; - int i; + __be32 header[13]; + int ret; - for (i = 0, s += 6; i < 13; i++, s += 8) - parsed[i] = simple_strntoul(s, NULL, 16, 8); + ret = hex2bin((u8 *)header, s + 6, sizeof(header)); + if (ret) { + error("damaged header"); + return ret; + } - ino = parsed[0]; - mode = parsed[1]; - uid = parsed[2]; - gid = parsed[3]; - nlink = parsed[4]; - mtime = parsed[5]; /* breaks in y2106 */ - body_len = parsed[6]; - major = parsed[7]; - minor = parsed[8]; - rdev = new_encode_dev(MKDEV(parsed[9], parsed[10])); - name_len = parsed[11]; - hdr_csum = parsed[12]; + ino = be32_to_cpu(header[0]); + mode = be32_to_cpu(header[1]); + uid = be32_to_cpu(header[2]); + gid = be32_to_cpu(header[3]); + nlink = be32_to_cpu(header[4]); + mtime = be32_to_cpu(header[5]); /* breaks in y2106 */ + body_len = be32_to_cpu(header[6]); + major = be32_to_cpu(header[7]); + minor = be32_to_cpu(header[8]); + rdev = new_encode_dev(MKDEV(be32_to_cpu(header[9]), be32_to_cpu(header[10]))); + name_len = be32_to_cpu(header[11]); + hdr_csum = be32_to_cpu(header[12]); + return 0; } /* Finite-state machine */ @@ -289,7 +296,8 @@ static int __init do_header(void) error("no cpio magic"); return 1; } - parse_header(collected); + if (parse_header(collected)) + return 1; next_header = this_header + N_ALIGN(name_len) + body_len; next_header = (next_header + 3) & ~3; state = SkipIt; diff --git a/init/initramfs_test.c b/init/initramfs_test.c index b9f83dc194aa..8a0ddc2db2c0 100644 --- a/init/initramfs_test.c +++ b/init/initramfs_test.c @@ -31,8 +31,8 @@ struct initramfs_test_cpio { #define CPIO_HDR_FMT "%s%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%s" /* * Bogus newc header with "0x" prefixes on the uid, gid, and namesize values. - * parse_header()/simple_str[n]toul() accept this, contrary to the initramfs - * specification. + * parse_header()/simple_str[n]toul() accepted this, contrary to the initramfs + * specification. hex2bin() now fails. */ #define CPIO_HDR_OX_INJECT \ "%s%08x%08x0x%06x0X%06x%08x%08x%08x%08x%08x%08x%08x0x%06x%08x%s" @@ -508,8 +508,7 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) { char *err; size_t len; - struct kstat st0 = {}, st1 = {}; - char fdata[] = "this file data will be unpacked"; + char fdata[] = "this file data will not be unpacked"; struct initramfs_test_bufs { char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2]; } *tbufs = kzalloc(sizeof(struct initramfs_test_bufs), GFP_KERNEL); @@ -539,22 +538,7 @@ static void __init initramfs_test_hdr_hex(struct kunit *test) len = fill_cpio(c, ARRAY_SIZE(c), true, tbufs->cpio_src); err = unpack_to_rootfs(tbufs->cpio_src, len); - KUNIT_EXPECT_NULL(test, err); - - KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); - KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); - - KUNIT_EXPECT_TRUE(test, - uid_eq(st0.uid, make_kuid(current_user_ns(), (uid_t)0x123456))); - KUNIT_EXPECT_TRUE(test, - gid_eq(st0.gid, make_kgid(current_user_ns(), (gid_t)0x123457))); - KUNIT_EXPECT_TRUE(test, - uid_eq(st1.uid, make_kuid(current_user_ns(), (uid_t)0x56))); - KUNIT_EXPECT_TRUE(test, - gid_eq(st1.gid, make_kgid(current_user_ns(), (gid_t)0x57))); - - KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); - KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); + KUNIT_EXPECT_NOT_NULL(test, err); kfree(tbufs); } From 54d30551e460b74b171a6d4a1b49157999d68247 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:35 +1100 Subject: [PATCH 5/6] vsprintf: Revert "add simple_strntoul" No users anymore and none should be in the first place. This reverts commit fcc155008a20fa31b01569e105250490750f0687. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-6-ddiss@suse.de Acked-by: Petr Mladek Reviewed-by: David Disseldorp Signed-off-by: Christian Brauner --- include/linux/kstrtox.h | 1 - lib/vsprintf.c | 7 ------- 2 files changed, 8 deletions(-) diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h index 6ea897222af1..7fcf29a4e0de 100644 --- a/include/linux/kstrtox.h +++ b/include/linux/kstrtox.h @@ -143,7 +143,6 @@ 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 9f359b31c8d1..a6169e9bcdc9 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -129,13 +129,6 @@ 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 From a2faa0e062db577a0b2e76bea632e924c63c132f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 31 Mar 2026 17:57:36 +1100 Subject: [PATCH 6/6] kstrtox: Drop extern keyword in the simple_strtox() declarations There is legacy 'extern' keyword for the exported simple_strtox() function which are the artefact that can be removed. So drop it. While at it, tweak the declaration to provide parameter names. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260331070519.5974-7-ddiss@suse.de Reviewed-by: David Disseldorp Reviewed-by: Petr Mladek Signed-off-by: Christian Brauner --- include/linux/kstrtox.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h index 7fcf29a4e0de..6c9282866770 100644 --- a/include/linux/kstrtox.h +++ b/include/linux/kstrtox.h @@ -142,9 +142,9 @@ static inline int __must_check kstrtos32_from_user(const char __user *s, size_t * Keep in mind above caveat. */ -extern unsigned long simple_strtoul(const char *,char **,unsigned int); -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); +unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base); +long simple_strtol(const char *cp, char **endp, unsigned int base); +unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base); +long long simple_strtoll(const char *cp, char **endp, unsigned int base); #endif /* _LINUX_KSTRTOX_H */