Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

initramfs_test: kunit tests for initramfs unpacking

Provide some basic initramfs unpack sanity tests covering:

- simple file / dir extraction
- filename field overrun, as reported and fixed separately via
https://lore.kernel.org/r/20241030035509.20194-2-ddiss@suse.de
- "070702" cpio data checksums
- hardlinks

Signed-off-by: David Disseldorp <ddiss@suse.de>
Link: https://lore.kernel.org/r/20250304061020.9815-3-ddiss@suse.de
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

David Disseldorp and committed by
Christian Brauner
83c0b272 5f469c4f

+418
+3
init/.kunitconfig
··· 1 + CONFIG_KUNIT=y 2 + CONFIG_BLK_DEV_INITRD=y 3 + CONFIG_INITRAMFS_TEST=y
+7
init/Kconfig
··· 1454 1454 1455 1455 If unsure, say Y. 1456 1456 1457 + config INITRAMFS_TEST 1458 + bool "Test initramfs cpio archive extraction" if !KUNIT_ALL_TESTS 1459 + depends on BLK_DEV_INITRD && KUNIT=y 1460 + default KUNIT_ALL_TESTS 1461 + help 1462 + Build KUnit tests for initramfs. See Documentation/dev-tools/kunit 1463 + 1457 1464 choice 1458 1465 prompt "Compiler optimization level" 1459 1466 default CC_OPTIMIZE_FOR_PERFORMANCE
+1
init/Makefile
··· 12 12 obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o 13 13 endif 14 14 obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o 15 + obj-$(CONFIG_INITRAMFS_TEST) += initramfs_test.o 15 16 16 17 obj-y += init_task.o 17 18
+407
init/initramfs_test.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 + 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 + 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 + 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 + */ 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 + 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 + 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 + */ 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))) 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");