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