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
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
initramfs_test_extract(struct kunit * test)65 static 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);
139 out:
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 */
initramfs_test_fname_overrun(struct kunit * test)148 static 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
initramfs_test_data(struct kunit * test)194 static 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);
240 out:
241 kfree(cpio_srcbuf);
242 }
243
initramfs_test_csum(struct kunit * test)244 static 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 */
initramfs_test_hardlink(struct kunit * test)305 static 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)))
initramfs_test_many(struct kunit * test)353 static 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 */
initramfs_test_fname_pad(struct kunit * test)397 static 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_obj(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);
446 out:
447 kfree(tbufs);
448 }
449
initramfs_test_fname_path_max(struct kunit * test)450 static void __init initramfs_test_fname_path_max(struct kunit *test)
451 {
452 char *err;
453 size_t len;
454 struct kstat st0, st1;
455 char fdata[] = "this file data will not be unpacked";
456 struct test_fname_path_max {
457 char fname_oversize[PATH_MAX + 1];
458 char fname_ok[PATH_MAX];
459 char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2];
460 } *tbufs = kzalloc_obj(struct test_fname_path_max, GFP_KERNEL);
461 struct initramfs_test_cpio c[] = { {
462 .magic = "070701",
463 .ino = 1,
464 .mode = S_IFDIR | 0777,
465 .nlink = 1,
466 .namesize = sizeof(tbufs->fname_oversize),
467 .fname = tbufs->fname_oversize,
468 .filesize = sizeof(fdata),
469 .data = fdata,
470 }, {
471 .magic = "070701",
472 .ino = 2,
473 .mode = S_IFDIR | 0777,
474 .nlink = 1,
475 .namesize = sizeof(tbufs->fname_ok),
476 .fname = tbufs->fname_ok,
477 } };
478
479 memset(tbufs->fname_oversize, '/', sizeof(tbufs->fname_oversize) - 1);
480 memset(tbufs->fname_ok, '/', sizeof(tbufs->fname_ok) - 1);
481 memcpy(tbufs->fname_oversize, "fname_oversize",
482 sizeof("fname_oversize") - 1);
483 memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1);
484 len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_src);
485
486 /* unpack skips over fname_oversize instead of returning an error */
487 err = unpack_to_rootfs(tbufs->cpio_src, len);
488 KUNIT_EXPECT_NULL(test, err);
489
490 KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT);
491 KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0);
492 KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0);
493
494 kfree(tbufs);
495 }
496
497 /*
498 * The kunit_case/_suite struct cannot be marked as __initdata as this will be
499 * used in debugfs to retrieve results after test has run.
500 */
501 static struct kunit_case __refdata initramfs_test_cases[] = {
502 KUNIT_CASE(initramfs_test_extract),
503 KUNIT_CASE(initramfs_test_fname_overrun),
504 KUNIT_CASE(initramfs_test_data),
505 KUNIT_CASE(initramfs_test_csum),
506 KUNIT_CASE(initramfs_test_hardlink),
507 KUNIT_CASE(initramfs_test_many),
508 KUNIT_CASE(initramfs_test_fname_pad),
509 KUNIT_CASE(initramfs_test_fname_path_max),
510 {},
511 };
512
513 static struct kunit_suite initramfs_test_suite = {
514 .name = "initramfs",
515 .test_cases = initramfs_test_cases,
516 };
517 kunit_test_init_section_suites(&initramfs_test_suite);
518
519 MODULE_DESCRIPTION("Initramfs KUnit test suite");
520 MODULE_LICENSE("GPL v2");
521