File Index
Files and directories
Sign in
FILE

initramfs_test.c

File preview with syntax highlighting.

linux-6.19.14/init/initramfs_test.c C
  1// SPDX-License-Identifier: GPL-2.0
  2#include <kunit/test.h>
  3#include <linux/fcntl.h>
  4#include <linux/file.h>
  5#include <linux/fs.h>
  6#include <linux/init_syscalls.h>
  7#include <linux/stringify.h>
  8#include <linux/timekeeping.h>
  9#include "initramfs_internal.h"
 10
 11struct initramfs_test_cpio {
 12	char *magic;
 13	unsigned int ino;
 14	unsigned int mode;
 15	unsigned int uid;
 16	unsigned int gid;
 17	unsigned int nlink;
 18	unsigned int mtime;
 19	unsigned int filesize;
 20	unsigned int devmajor;
 21	unsigned int devminor;
 22	unsigned int rdevmajor;
 23	unsigned int rdevminor;
 24	unsigned int namesize;
 25	unsigned int csum;
 26	char *fname;
 27	char *data;
 28};
 29
 30static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out)
 31{
 32	int i;
 33	size_t off = 0;
 34
 35	for (i = 0; i < csz; i++) {
 36		char *pos = &out[off];
 37		struct initramfs_test_cpio *c = &cs[i];
 38		size_t thislen;
 39
 40		/* +1 to account for nulterm */
 41		thislen = sprintf(pos, "%s"
 42			"%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x"
 43			"%s",
 44			c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink,
 45			c->mtime, c->filesize, c->devmajor, c->devminor,
 46			c->rdevmajor, c->rdevminor, c->namesize, c->csum,
 47			c->fname) + 1;
 48
 49		pr_debug("packing (%zu): %.*s\n", thislen, (int)thislen, pos);
 50		if (thislen != CPIO_HDRLEN + c->namesize)
 51			pr_debug("padded to: %u\n", CPIO_HDRLEN + c->namesize);
 52		off += CPIO_HDRLEN + c->namesize;
 53		while (off & 3)
 54			out[off++] = '\0';
 55
 56		memcpy(&out[off], c->data, c->filesize);
 57		off += c->filesize;
 58		while (off & 3)
 59			out[off++] = '\0';
 60	}
 61
 62	return off;
 63}
 64
 65static void __init initramfs_test_extract(struct kunit *test)
 66{
 67	char *err, *cpio_srcbuf;
 68	size_t len;
 69	struct timespec64 ts_before, ts_after;
 70	struct kstat st = {};
 71	struct initramfs_test_cpio c[] = { {
 72		.magic = "070701",
 73		.ino = 1,
 74		.mode = S_IFREG | 0777,
 75		.uid = 12,
 76		.gid = 34,
 77		.nlink = 1,
 78		.mtime = 56,
 79		.filesize = 0,
 80		.devmajor = 0,
 81		.devminor = 1,
 82		.rdevmajor = 0,
 83		.rdevminor = 0,
 84		.namesize = sizeof("initramfs_test_extract"),
 85		.csum = 0,
 86		.fname = "initramfs_test_extract",
 87	}, {
 88		.magic = "070701",
 89		.ino = 2,
 90		.mode = S_IFDIR | 0777,
 91		.nlink = 1,
 92		.mtime = 57,
 93		.devminor = 1,
 94		.namesize = sizeof("initramfs_test_extract_dir"),
 95		.fname = "initramfs_test_extract_dir",
 96	}, {
 97		.magic = "070701",
 98		.namesize = sizeof("TRAILER!!!"),
 99		.fname = "TRAILER!!!",
100	} };
101
102	/* +3 to cater for any 4-byte end-alignment */
103	cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3),
104			      GFP_KERNEL);
105	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
106
107	ktime_get_real_ts64(&ts_before);
108	err = unpack_to_rootfs(cpio_srcbuf, len);
109	ktime_get_real_ts64(&ts_after);
110	if (err) {
111		KUNIT_FAIL(test, "unpack failed %s", err);
112		goto out;
113	}
114
115	KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0);
116	KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode));
117	KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid)));
118	KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid)));
119	KUNIT_EXPECT_EQ(test, st.nlink, 1);
120	if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
121		KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime);
122	} else {
123		KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
124		KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
125	}
126	KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize);
127
128	KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0);
129	KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode));
130	if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
131		KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime);
132	} else {
133		KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
134		KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
135	}
136
137	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
138	KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0);
139out:
140	kfree(cpio_srcbuf);
141}
142
143/*
144 * Don't terminate filename. Previously, the cpio filename field was passed
145 * directly to filp_open(collected, O_CREAT|..) without nulterm checks. See
146 * https://lore.kernel.org/linux-fsdevel/20241030035509.20194-2-ddiss@suse.de
147 */
148static void __init initramfs_test_fname_overrun(struct kunit *test)
149{
150	char *err, *cpio_srcbuf;
151	size_t len, suffix_off;
152	struct initramfs_test_cpio c[] = { {
153		.magic = "070701",
154		.ino = 1,
155		.mode = S_IFREG | 0777,
156		.uid = 0,
157		.gid = 0,
158		.nlink = 1,
159		.mtime = 1,
160		.filesize = 0,
161		.devmajor = 0,
162		.devminor = 1,
163		.rdevmajor = 0,
164		.rdevminor = 0,
165		.namesize = sizeof("initramfs_test_fname_overrun"),
166		.csum = 0,
167		.fname = "initramfs_test_fname_overrun",
168	} };
169
170	/*
171	 * poison cpio source buffer, so we can detect overrun. source
172	 * buffer is used by read_into() when hdr or fname
173	 * are already available (e.g. no compression).
174	 */
175	cpio_srcbuf = kmalloc(CPIO_HDRLEN + PATH_MAX + 3, GFP_KERNEL);
176	memset(cpio_srcbuf, 'B', CPIO_HDRLEN + PATH_MAX + 3);
177	/* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */
178	cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0';
179
180	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
181	/* overwrite trailing fname terminator and padding */
182	suffix_off = len - 1;
183	while (cpio_srcbuf[suffix_off] == '\0') {
184		cpio_srcbuf[suffix_off] = 'P';
185		suffix_off--;
186	}
187
188	err = unpack_to_rootfs(cpio_srcbuf, len);
189	KUNIT_EXPECT_NOT_NULL(test, err);
190
191	kfree(cpio_srcbuf);
192}
193
194static void __init initramfs_test_data(struct kunit *test)
195{
196	char *err, *cpio_srcbuf;
197	size_t len;
198	struct file *file;
199	struct initramfs_test_cpio c[] = { {
200		.magic = "070701",
201		.ino = 1,
202		.mode = S_IFREG | 0777,
203		.uid = 0,
204		.gid = 0,
205		.nlink = 1,
206		.mtime = 1,
207		.filesize = sizeof("ASDF") - 1,
208		.devmajor = 0,
209		.devminor = 1,
210		.rdevmajor = 0,
211		.rdevminor = 0,
212		.namesize = sizeof("initramfs_test_data"),
213		.csum = 0,
214		.fname = "initramfs_test_data",
215		.data = "ASDF",
216	} };
217
218	/* +6 for max name and data 4-byte padding */
219	cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6,
220			      GFP_KERNEL);
221
222	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
223
224	err = unpack_to_rootfs(cpio_srcbuf, len);
225	KUNIT_EXPECT_NULL(test, err);
226
227	file = filp_open(c[0].fname, O_RDONLY, 0);
228	if (IS_ERR(file)) {
229		KUNIT_FAIL(test, "open failed");
230		goto out;
231	}
232
233	/* read back file contents into @cpio_srcbuf and confirm match */
234	len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL);
235	KUNIT_EXPECT_EQ(test, len, c[0].filesize);
236	KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len);
237
238	fput(file);
239	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
240out:
241	kfree(cpio_srcbuf);
242}
243
244static void __init initramfs_test_csum(struct kunit *test)
245{
246	char *err, *cpio_srcbuf;
247	size_t len;
248	struct initramfs_test_cpio c[] = { {
249		/* 070702 magic indicates a valid csum is present */
250		.magic = "070702",
251		.ino = 1,
252		.mode = S_IFREG | 0777,
253		.nlink = 1,
254		.filesize = sizeof("ASDF") - 1,
255		.devminor = 1,
256		.namesize = sizeof("initramfs_test_csum"),
257		.csum = 'A' + 'S' + 'D' + 'F',
258		.fname = "initramfs_test_csum",
259		.data = "ASDF",
260	}, {
261		/* mix csum entry above with no-csum entry below */
262		.magic = "070701",
263		.ino = 2,
264		.mode = S_IFREG | 0777,
265		.nlink = 1,
266		.filesize = sizeof("ASDF") - 1,
267		.devminor = 1,
268		.namesize = sizeof("initramfs_test_csum_not_here"),
269		/* csum ignored */
270		.csum = 5555,
271		.fname = "initramfs_test_csum_not_here",
272		.data = "ASDF",
273	} };
274
275	cpio_srcbuf = kmalloc(8192, GFP_KERNEL);
276
277	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
278
279	err = unpack_to_rootfs(cpio_srcbuf, len);
280	KUNIT_EXPECT_NULL(test, err);
281
282	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
283	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
284
285	/* mess up the csum and confirm that unpack fails */
286	c[0].csum--;
287	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
288
289	err = unpack_to_rootfs(cpio_srcbuf, len);
290	KUNIT_EXPECT_NOT_NULL(test, err);
291
292	/*
293	 * file (with content) is still retained in case of bad-csum abort.
294	 * Perhaps we should change this.
295	 */
296	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
297	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT);
298	kfree(cpio_srcbuf);
299}
300
301/*
302 * hardlink hashtable may leak when the archive omits a trailer:
303 * https://lore.kernel.org/r/20241107002044.16477-10-ddiss@suse.de/
304 */
305static void __init initramfs_test_hardlink(struct kunit *test)
306{
307	char *err, *cpio_srcbuf;
308	size_t len;
309	struct kstat st0, st1;
310	struct initramfs_test_cpio c[] = { {
311		.magic = "070701",
312		.ino = 1,
313		.mode = S_IFREG | 0777,
314		.nlink = 2,
315		.devminor = 1,
316		.namesize = sizeof("initramfs_test_hardlink"),
317		.fname = "initramfs_test_hardlink",
318	}, {
319		/* hardlink data is present in last archive entry */
320		.magic = "070701",
321		.ino = 1,
322		.mode = S_IFREG | 0777,
323		.nlink = 2,
324		.filesize = sizeof("ASDF") - 1,
325		.devminor = 1,
326		.namesize = sizeof("initramfs_test_hardlink_link"),
327		.fname = "initramfs_test_hardlink_link",
328		.data = "ASDF",
329	} };
330
331	cpio_srcbuf = kmalloc(8192, GFP_KERNEL);
332
333	len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
334
335	err = unpack_to_rootfs(cpio_srcbuf, len);
336	KUNIT_EXPECT_NULL(test, err);
337
338	KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0);
339	KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0);
340	KUNIT_EXPECT_EQ(test, st0.ino, st1.ino);
341	KUNIT_EXPECT_EQ(test, st0.nlink, 2);
342	KUNIT_EXPECT_EQ(test, st1.nlink, 2);
343
344	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
345	KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
346
347	kfree(cpio_srcbuf);
348}
349
350#define INITRAMFS_TEST_MANY_LIMIT 1000
351#define INITRAMFS_TEST_MANY_PATH_MAX (sizeof("initramfs_test_many-") \
352			+ sizeof(__stringify(INITRAMFS_TEST_MANY_LIMIT)))
353static void __init initramfs_test_many(struct kunit *test)
354{
355	char *err, *cpio_srcbuf, *p;
356	size_t len = INITRAMFS_TEST_MANY_LIMIT *
357		     (CPIO_HDRLEN + INITRAMFS_TEST_MANY_PATH_MAX + 3);
358	char thispath[INITRAMFS_TEST_MANY_PATH_MAX];
359	int i;
360
361	p = cpio_srcbuf = kmalloc(len, GFP_KERNEL);
362
363	for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
364		struct initramfs_test_cpio c = {
365			.magic = "070701",
366			.ino = i,
367			.mode = S_IFREG | 0777,
368			.nlink = 1,
369			.devminor = 1,
370			.fname = thispath,
371		};
372
373		c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i);
374		p += fill_cpio(&c, 1, p);
375	}
376
377	len = p - cpio_srcbuf;
378	err = unpack_to_rootfs(cpio_srcbuf, len);
379	KUNIT_EXPECT_NULL(test, err);
380
381	for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
382		sprintf(thispath, "initramfs_test_many-%d", i);
383		KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0);
384	}
385
386	kfree(cpio_srcbuf);
387}
388
389/*
390 * An initramfs filename is namesize in length, including the zero-terminator.
391 * A filename can be zero-terminated prior to namesize, with the remainder used
392 * as padding. This can be useful for e.g. alignment of file data segments with
393 * a 4KB filesystem block, allowing for extent sharing (reflinks) between cpio
394 * source and destination. This hack works with both GNU cpio and initramfs, as
395 * long as PATH_MAX isn't exceeded.
396 */
397static void __init initramfs_test_fname_pad(struct kunit *test)
398{
399	char *err;
400	size_t len;
401	struct file *file;
402	char fdata[] = "this file data is aligned at 4K in the archive";
403	struct test_fname_pad {
404		char padded_fname[4096 - CPIO_HDRLEN];
405		char cpio_srcbuf[CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)];
406	} *tbufs = kzalloc(sizeof(struct test_fname_pad), GFP_KERNEL);
407	struct initramfs_test_cpio c[] = { {
408		.magic = "070701",
409		.ino = 1,
410		.mode = S_IFREG | 0777,
411		.uid = 0,
412		.gid = 0,
413		.nlink = 1,
414		.mtime = 1,
415		.filesize = sizeof(fdata),
416		.devmajor = 0,
417		.devminor = 1,
418		.rdevmajor = 0,
419		.rdevminor = 0,
420		/* align file data at 4K archive offset via padded fname */
421		.namesize = 4096 - CPIO_HDRLEN,
422		.csum = 0,
423		.fname = tbufs->padded_fname,
424		.data = fdata,
425	} };
426
427	memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname"));
428	len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf);
429
430	err = unpack_to_rootfs(tbufs->cpio_srcbuf, len);
431	KUNIT_EXPECT_NULL(test, err);
432
433	file = filp_open(c[0].fname, O_RDONLY, 0);
434	if (IS_ERR(file)) {
435		KUNIT_FAIL(test, "open failed");
436		goto out;
437	}
438
439	/* read back file contents into @cpio_srcbuf and confirm match */
440	len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL);
441	KUNIT_EXPECT_EQ(test, len, c[0].filesize);
442	KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len);
443
444	fput(file);
445	KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
446out:
447	kfree(tbufs);
448}
449
450/*
451 * The kunit_case/_suite struct cannot be marked as __initdata as this will be
452 * used in debugfs to retrieve results after test has run.
453 */
454static struct kunit_case __refdata initramfs_test_cases[] = {
455	KUNIT_CASE(initramfs_test_extract),
456	KUNIT_CASE(initramfs_test_fname_overrun),
457	KUNIT_CASE(initramfs_test_data),
458	KUNIT_CASE(initramfs_test_csum),
459	KUNIT_CASE(initramfs_test_hardlink),
460	KUNIT_CASE(initramfs_test_many),
461	KUNIT_CASE(initramfs_test_fname_pad),
462	{},
463};
464
465static struct kunit_suite initramfs_test_suite = {
466	.name = "initramfs",
467	.test_cases = initramfs_test_cases,
468};
469kunit_test_init_section_suites(&initramfs_test_suite);
470
471MODULE_DESCRIPTION("Initramfs KUnit test suite");
472MODULE_LICENSE("GPL v2");
~/ Read-only file preview.