Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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");