[PATCH v6 3/4] initramfs: read metadata from special file METADATA!!!

From: Danny Hu

Date: Thu Nov 05 2026 - 14:52:14 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

Instead of changing the CPIO format, metadata are parsed from regular files
with special name 'METADATA!!!'. This file immediately follows the file
metadata are added to.

This patch checks if the file being extracted has the special name and, if
yes, creates a buffer with the content of that file and transitions
to a new dedicated CollectMetadata state to buffer the metadata contents
in memory without creating a file in the rootfs.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
Reported-by: kbuild test robot <lkp@xxxxxxxxx>
Signed-off-by: Tianxing Zhang <zhangtianxing3@xxxxxxxxxx>
Signed-off-by: Zheng Zengkai <zhengzengkai@xxxxxxxxxx>
Signed-off-by: zhoushuiqing <zhoushuiqing2@xxxxxxxxxx>
Signed-off-by: zhangguangzhi <zhangguangzhi3@xxxxxxxxxx>
Signed-off-by: Danny Hu <dannyhu@xxxxxxxxxx>
---
v6 changes:
- Introduce a dedicated CollectMetadata state to collect METADATA!!! entries
in memory. This state replaces the metadata flag and eliminates the need to
reset it on failure paths. Since metadata is collected purely in memory, the
entry is no longer extracted as a regular file to the root of the archive.
This also has the benefit of separating metadata collection from do_copy.
- Validate the checksum before setting the xattr with do_parse_metadata
- Improve bookkeeping of previous_name_buf by setting it only after the target
object is created. Clear the buffer on skipped or failed entries to prevent
setting metadata on the wrong target. Indicate that previous_name_buf is
invalid/not set by having the first byte be the NUL character
- Remove "Reviewed-by: Jason Yan <yanaijie@xxxxxxxxxx>" because patch has
implementation has significantly changed in the v6 revision

v5 changes:
- clean up metadata buffer when returning from unpack_to_rootfs()

init/initramfs.c | 103 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 90 insertions(+), 13 deletions(-)

diff --git a/init/initramfs.c b/init/initramfs.c
index d206624d1160..a17c26fde5f5 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -34,6 +34,17 @@
static __initdata bool csum_present;
static __initdata u32 io_csum;

+static void __init update_csum(const unsigned char *buf, size_t len)
+{
+ size_t i;
+
+ if (!csum_present)
+ return;
+
+ for (i = 0; i < len; i++)
+ io_csum += buf[i];
+}
+
static ssize_t __init xwrite(struct file *file, const unsigned char *p,
size_t count, loff_t *pos)
{
@@ -50,12 +61,7 @@ static ssize_t __init xwrite(struct file *file, const unsigned char *p,
} else if (rv == 0)
break;

- if (csum_present) {
- ssize_t i;
-
- for (i = 0; i < rv; i++)
- io_csum += p[i];
- }
+ update_csum(p, rv);

p += rv;
out += rv;
@@ -233,6 +239,7 @@ static __initdata enum state {
SkipIt,
GotName,
CopyFile,
+ CollectMetadata,
GotSymlink,
Reset
} state, next_state;
@@ -266,7 +273,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, *symlink_buf, *name_buf, *previous_name_buf;
static __initdata char *metadata_buf;

static int __init do_start(void)
@@ -308,10 +315,10 @@ static int __init do_header(void)
next_header = (next_header + 3) & ~3;
state = SkipIt;
if (name_len <= 0 || name_len > PATH_MAX)
- return 0;
+ goto skip;
if (S_ISLNK(mode)) {
if (body_len > PATH_MAX)
- return 0;
+ goto skip;
collect = collected = symlink_buf;
remains = N_ALIGN(name_len) + body_len;
next_state = GotSymlink;
@@ -320,6 +327,11 @@ static int __init do_header(void)
}
if (S_ISREG(mode) || !body_len)
read_into(name_buf, N_ALIGN(name_len), GotName);
+ else
+ goto skip;
+ return 0;
+skip:
+ previous_name_buf[0] = '\0';
return 0;
}

@@ -471,6 +483,8 @@ static __initdata loff_t wfile_pos;

static int __init do_name(void)
{
+ bool target_ready = false;
+
state = SkipIt;
next_state = Reset;

@@ -482,6 +496,19 @@ static int __init do_name(void)
return 1;
}

+ if (S_ISREG(mode) && strcmp(collected, METADATA_FILENAME) == 0) {
+ metadata_len = body_len;
+ if (metadata_len)
+ metadata_buf = kmalloc(metadata_len, GFP_KERNEL);
+ else
+ metadata_buf = NULL;
+ io_csum = 0;
+ state = CollectMetadata;
+ return 0;
+ }
+
+ /* Clear metadata target only after checking entry is not metadata */
+ previous_name_buf[0] = '\0';
if (strcmp(collected, "TRAILER!!!") == 0) {
free_hash();
return 0;
@@ -504,24 +531,65 @@ static int __init do_name(void)
if (body_len)
vfs_truncate(&wfile->f_path, body_len);
state = CopyFile;
+ target_ready = true;
}
} else if (S_ISDIR(mode)) {
- init_mkdir(collected, mode);
+ int ret = init_mkdir(collected, mode);
+
+ target_ready = !ret || ret == -EEXIST;
init_chown(collected, uid, gid, 0);
init_chmod(collected, mode);
dir_add(collected, name_len, mtime);
} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
S_ISFIFO(mode) || S_ISSOCK(mode)) {
- if (maybe_link() == 0) {
- init_mknod(collected, mode, rdev);
+ int ml = maybe_link();
+
+ if (ml == 0) {
+ int ret = init_mknod(collected, mode, rdev);
+
+ target_ready = !ret || ret == -EEXIST;
init_chown(collected, uid, gid, 0);
init_chmod(collected, mode);
do_utime(collected, mtime);
+ } else if (ml == 1) {
+ target_ready = true;
}
}
+ if (target_ready)
+ memcpy(previous_name_buf, collected, strlen(collected) + 1);
return 0;
}

+static void __init do_process_metadata(const char *buf, size_t len)
+{
+ /* Still checksum the body if allocation failed. */
+ update_csum((const unsigned char *)buf, len);
+ if (metadata_buf)
+ memcpy(metadata_buf + (metadata_len - body_len), buf, len);
+}
+
+static int __init do_collect_metadata(void)
+{
+ if (byte_count >= body_len) {
+ do_process_metadata(victim, body_len);
+ if (csum_present && io_csum != hdr_csum)
+ error("bad data checksum");
+ else if (metadata_buf && previous_name_buf[0])
+ do_parse_metadata(previous_name_buf);
+ kfree(metadata_buf);
+ metadata_buf = NULL;
+ metadata_len = 0;
+ eat(body_len);
+ state = SkipIt;
+ return 0;
+ }
+
+ do_process_metadata(victim, byte_count);
+ body_len -= byte_count;
+ eat(byte_count);
+ return 1;
+}
+
static int __init do_copy(void)
{
if (byte_count >= body_len) {
@@ -546,6 +614,7 @@ static int __init do_copy(void)

static int __init do_symlink(void)
{
+ previous_name_buf[0] = '\0';
if (collected[name_len - 1] != '\0') {
pr_err("initramfs symlink without nulterm: %.*s\n",
(int)name_len, collected);
@@ -554,7 +623,8 @@ static int __init do_symlink(void)
}
collected[N_ALIGN(name_len) + body_len] = '\0';
clean_path(collected, 0);
- init_symlink(collected + N_ALIGN(name_len), collected);
+ if (!init_symlink(collected + N_ALIGN(name_len), collected))
+ memcpy(previous_name_buf, collected, strlen(collected) + 1);
init_chown(collected, uid, gid, AT_SYMLINK_NOFOLLOW);
do_utime(collected, mtime);
state = SkipIt;
@@ -569,6 +639,7 @@ static __initdata int (*actions[])(void) = {
[SkipIt] = do_skip,
[GotName] = do_name,
[CopyFile] = do_copy,
+ [CollectMetadata] = do_collect_metadata,
[GotSymlink] = do_symlink,
[Reset] = do_reset,
};
@@ -628,6 +699,7 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
char header[CPIO_HDRLEN];
char symlink[PATH_MAX + N_ALIGN(PATH_MAX) + 1];
char name[N_ALIGN(PATH_MAX)];
+ char prev_name[PATH_MAX + N_ALIGN(PATH_MAX) + 1];
} *bufs = kmalloc_obj(*bufs);

if (!bufs)
@@ -636,6 +708,8 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
header_buf = bufs->header;
symlink_buf = bufs->symlink;
name_buf = bufs->name;
+ previous_name_buf = bufs->prev_name;
+ previous_name_buf[0] = '\0';

state = Start;
this_header = 0;
@@ -678,6 +752,9 @@ char * __init unpack_to_rootfs(char *buf, unsigned long len)
dir_utime();
/* free any hardlink state collected without optional TRAILER!!! */
free_hash();
+ kfree(metadata_buf);
+ metadata_buf = NULL;
+ metadata_len = 0;
kfree(bufs);
return message;
}
--
2.51.0