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